Skip to content

Instantly share code, notes, and snippets.

@mzahidriaz
Last active July 5, 2026 22:27
Show Gist options
  • Select an option

  • Save mzahidriaz/4c5404fe24e3c6a00d7bd82b3ca328e7 to your computer and use it in GitHub Desktop.

Select an option

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();
}
}
@herduin

herduin commented Aug 15, 2023

Copy link
Copy Markdown

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
Copy Markdown

Very Nice @herduin

@durga18584

Copy link
Copy Markdown

@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
Copy Markdown

Thank you @herduin

@otema666

Copy link
Copy Markdown

Congrats! @herduin

@chandimasw

Copy link
Copy Markdown

thank you @herduin. Works Great.

@ftonato

ftonato commented Dec 27, 2023

Copy link
Copy Markdown

Very Nice @herduin

@benh1979

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
Author

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

@Eraldo

Eraldo commented Mar 4, 2024

Copy link
Copy Markdown

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

@ChaT0n

ChaT0n commented Apr 9, 2024

Copy link
Copy Markdown

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

@Sorbh

Sorbh commented Jun 2, 2024

Copy link
Copy Markdown

@ChaT0n Create a bookmark with any name and enter this whole code in url

javascript:(function()%7Bvar%20_contacts%20%3D%20%5B%5D%3B%0Aclass%20ContactFinder%20%7B%0A%20%20%23db%3B%0A%20%20%23chatToFind%3B%0A%20%20%23dbName%20%3D%20%22model-storage%22%3B%0A%20%20%23chatsCol%20%3D%20%22chat%22%3B%0A%20%20%23contactCol%20%3D%20%22contact%22%3B%0A%20%20%23groupCol%20%3D%20%22participant%22%3B%0A%0A%20%20constructor(chatGroupName)%20%7B%0A%20%20%20%20this.%23chatToFind%20%3D%20chatGroupName%3B%0A%20%20%7D%0A%0A%20%20async%20openConnection()%20%7B%0A%20%20%20%20if%20(!this.%23db)%20%7B%0A%20%20%20%20%20%20const%20dbName%20%3D%20this.%23dbName%3B%0A%20%20%20%20%20%20this.%23db%20%3D%20await%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20request%20%3D%20indexedDB.open(dbName)%3B%0A%20%20%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20this.%23db%3B%0A%20%20%7D%0A%0A%20%20async%20%23promisifyCol(collection%2C%20query%2C%20count)%20%7B%0A%20%20%20%20const%20db%20%3D%20await%20this.openConnection()%3B%0A%20%20%20%20return%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20request%20%3D%20db.transaction(collection).objectStore(collection).getAll(query%2C%20count)%3B%0A%0A%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getChats()%20%7B%0A%20%20%20%20const%20allChats%20%3D%20await%20this.%23promisifyCol(this.%23chatsCol)%3B%0A%20%20%20%20const%20chatToFind%20%3D%20this.%23chatToFind%3B%0A%20%20%20%20return%20allChats.filter((chat)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20chat.name%20%26%26%20chat.name.includes(chatToFind)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroups()%20%7B%0A%20%20%20%20const%20chats%20%3D%20(await%20this.%23getChats()).map((chat)%20%3D%3E%20chat.id)%3B%0A%20%20%20%20const%20allGroups%20%3D%20await%20this.%23promisifyCol(this.%23groupCol)%3B%0A%0A%20%20%20%20return%20allGroups.filter((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20group.groupId%20%26%26%20chats.includes(group.groupId)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroupParticipants()%20%7B%0A%20%20%20%20const%20groups%20%3D%20await%20this.%23getGroups()%3B%0A%20%20%20%20const%20map%20%3D%20new%20Map()%3B%0A%0A%20%20%20%20groups.forEach((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20group.participants.forEach((par)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20par.replace(%22%40c.us%22%2C%20%22%22)%3B%0A%20%20%20%20%20%20%20%20map.set(num%2C%20num)%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20return%20map%3B%0A%20%20%7D%0A%0A%20%20async%20%23getContacts()%20%7B%0A%20%20%20%20return%20this.%23promisifyCol(this.%23contactCol)%3B%0A%20%20%7D%0A%0A%20%20async%20getGroupMembers()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.%23getGroupParticipants()%3B%0A%20%20%20%20const%20contacts%20%3D%20await%20this.%23getContacts()%3B%0A%0A%20%20%20%20contacts.forEach((contact)%20%3D%3E%20%7B%0A%20%20%20%20%20%20var%20num%3B%0A%20%20%20%20%20%20if%20(contact.phoneNumber)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.phoneNumber.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(contact.id)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.id.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(num%20%26%26%20members.get(num))%20%7B%0A%20%20%20%20%20%20%20%20var%20m%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20phoneNum%3A%20num%2C%0A%20%20%20%20%20%20%20%20%20%20name%3A%20contact.name%2C%0A%20%20%20%20%20%20%20%20%20%20pushname%3A%20contact.pushname%2C%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20members.set(num%2C%20m)%3B%0A%20%20%20%20%20%20%20%20_contacts.push(m)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%20%20%20%20return%20members%3B%0A%20%20%7D%0A%0A%0A%20%20async%20downloadMembersAsCSV()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.getGroupMembers()%3B%0A%20%20%20%20let%20csvContent%20%3D%20%22data%3Atext%2Fcsv%3Bcharset%3Dutf-8%2C%22%3B%0A%20%20%20%20%20%20%20%20csvContent%20%2B%3D%20'%22Phone%22%2C%22Name%22%2C%22Push%20Name%22%5Cr%5Cn'%3B%0A%0A%20%20%20%20for%20(const%20%5Bkey%2C%20value%5D%20of%20members.entries())%20%7B%0A%20%20%20%20%20%20const%20values%20%3D%20%5B%0A%20%20%20%20%20%20%20%20value.phoneNum%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.name%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.pushname%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%5D%3B%0A%20%20%20%20%20%20const%20row%20%3D%20values.map((value)%20%3D%3E%20%60%22%24%7Bvalue%7D%22%60).join(%22%2C%22)%3B%0A%20%20%20%20%20%20csvContent%20%2B%3D%20row%20%2B%20%22%5Cr%5Cn%22%3B%0A%20%20%20%20%7D%0A%20%20%20%20console.log(csvContent)%3B%0A%0A%20%20%20%20const%20encodedUri%20%3D%20encodeURI(csvContent)%3B%0A%20%20%20%20var%20link%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20link.setAttribute(%22href%22%2C%20encodedUri)%3B%0A%20%20%20%20link.setAttribute(%22download%22%2C%20%22WhatsAppContacts.csv%22)%3B%0A%20%20%20%20document.body.appendChild(link)%3B%20%2F%2F%20Required%20for%20FF%0A%20%20%20%20link.click()%3B%0A%20%20%7D%0A%0A%0A%0A%7D%0A%0A(async%20()%20%3D%3E%20%7B%0A%20%20const%20contactFinder%20%3D%20new%20ContactFinder(window.prompt(%22Nombre%20del%20grupo%3A%22))%3B%0A%20%20const%20members%20%3D%20await%20contactFinder.getGroupMembers()%3B%20%2F%2F%20This%20will%20return%20a%20JS%20Map%20Object%0A%20%20await%20contactFinder.downloadMembersAsCSV()%3B%20%2F%2F%20This%20will%20download%20the%20contacts%20as%20CSV%0A%20%20console.log(_contacts)%3B%0A%7D)()%3B%7D)()%3B

Open the whats web now and click on this bookmark. It will ask for the group name, put the exact group name you want to export contacts for education purpose. bookmark will prompt you to save the csv file.

@fabio-weydson

Copy link
Copy Markdown

@Sorbh well done!

@ChaT0n

ChaT0n commented Jun 19, 2024

Copy link
Copy Markdown

@Sorbh will try it at the end of the week, thx

@azOt1707

Copy link
Copy Markdown

Я создал этот вариант для книжного рынка, чтобы упростить использование. Вот книжный рынок Получить контакты:

А вот и код:

// 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);
})();

Screenshot 2024-07-27 142353
Why are my phone numbers not being retrieved? Just empty

@azOt1707

Copy link
Copy Markdown

javascript:(function()%7Bvar%20_contacts%20%3D%20%5B%5D%3B%0Aclass%20ContactFinder%20%7B%0A%20%20%23db%3B%0A%20%20%23chatToFind%3B%0A%20%20%23dbName%20%3D%20%22model-storage%22%3B%0A%20%20%23chatsCol%20%3D%20%22chat%22%3B%0A%20%20%23contactCol%20%3D%20%22contact%22%3B%0A%20%20%23groupCol%20%3D%20%22participant%22%3B%0A%0A%20%20constructor(chatGroupName)%20%7B%0A%20%20%20%20this.%23chatToFind%20%3D%20chatGroupName%3B%0A%20%20%7D%0A%0A%20%20async%20openConnection()%20%7B%0A%20%20%20%20if%20(!this.%23db)%20%7B%0A%20%20%20%20%20%20const%20dbName%20%3D%20this.%23dbName%3B%0A%20%20%20%20%20%20this.%23db%20%3D%20await%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20request%20%3D%20indexedDB.open(dbName)%3B%0A%20%20%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20this.%23db%3B%0A%20%20%7D%0A%0A%20%20async%20%23promisifyCol(collection%2C%20query%2C%20count)%20%7B%0A%20%20%20%20const%20db%20%3D%20await%20this.openConnection()%3B%0A%20%20%20%20return%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20request%20%3D%20db.transaction(collection).objectStore(collection).getAll(query%2C%20count)%3B%0A%0A%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getChats()%20%7B%0A%20%20%20%20const%20allChats%20%3D%20await%20this.%23promisifyCol(this.%23chatsCol)%3B%0A%20%20%20%20const%20chatToFind%20%3D%20this.%23chatToFind%3B%0A%20%20%20%20return%20allChats.filter((chat)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20chat.name%20%26%26%20chat.name.includes(chatToFind)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroups()%20%7B%0A%20%20%20%20const%20chats%20%3D%20(await%20this.%23getChats()).map((chat)%20%3D%3E%20chat.id)%3B%0A%20%20%20%20const%20allGroups%20%3D%20await%20this.%23promisifyCol(this.%23groupCol)%3B%0A%0A%20%20%20%20return%20allGroups.filter((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20group.groupId%20%26%26%20chats.includes(group.groupId)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroupParticipants()%20%7B%0A%20%20%20%20const%20groups%20%3D%20await%20this.%23getGroups()%3B%0A%20%20%20%20const%20map%20%3D%20new%20Map()%3B%0A%0A%20%20%20%20groups.forEach((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20group.participants.forEach((par)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20par.replace(%22%40c.us%22%2C%20%22%22)%3B%0A%20%20%20%20%20%20%20%20map.set(num%2C%20num)%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20return%20map%3B%0A%20%20%7D%0A%0A%20%20async%20%23getContacts()%20%7B%0A%20%20%20%20return%20this.%23promisifyCol(this.%23contactCol)%3B%0A%20%20%7D%0A%0A%20%20async%20getGroupMembers()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.%23getGroupParticipants()%3B%0A%20%20%20%20const%20contacts%20%3D%20await%20this.%23getContacts()%3B%0A%0A%20%20%20%20contacts.forEach((contact)%20%3D%3E%20%7B%0A%20%20%20%20%20%20var%20num%3B%0A%20%20%20%20%20%20if%20(contact.phoneNumber)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.phoneNumber.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(contact.id)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.id.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(num%20%26%26%20members.get(num))%20%7B%0A%20%20%20%20%20%20%20%20var%20m%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20phoneNum%3A%20num%2C%0A%20%20%20%20%20%20%20%20%20%20name%3A%20contact.name%2C%0A%20%20%20%20%20%20%20%20%20%20pushname%3A%20contact.pushname%2C%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20members.set(num%2C%20m)%3B%0A%20%20%20%20%20%20%20%20_contacts.push(m)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%20%20%20%20return%20members%3B%0A%20%20%7D%0A%0A%0A%20%20async%20downloadMembersAsCSV()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.getGroupMembers()%3B%0A%20%20%20%20let%20csvContent%20%3D%20%22data%3Atext%2Fcsv%3Bcharset%3Dutf-8%2C%22%3B%0A%20%20%20%20%20%20%20%20csvContent%20%2B%3D%20'%22Phone%22%2C%22Name%22%2C%22Push%20Name%22%5Cr%5Cn'%3B%0A%0A%20%20%20%20for%20(const%20%5Bkey%2C%20value%5D%20of%20members.entries())%20%7B%0A%20%20%20%20%20%20const%20values%20%3D%20%5B%0A%20%20%20%20%20%20%20%20value.phoneNum%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.name%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.pushname%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%5D%3B%0A%20%20%20%20%20%20const%20row%20%3D%20values.map((value)%20%3D%3E%20%60%22%24%7Bvalue%7D%22%60).join(%22%2C%22)%3B%0A%20%20%20%20%20%20csvContent%20%2B%3D%20row%20%2B%20%22%5Cr%5Cn%22%3B%0A%20%20%20%20%7D%0A%20%20%20%20console.log(csvContent)%3B%0A%0A%20%20%20%20const%20encodedUri%20%3D%20encodeURI(csvContent)%3B%0A%20%20%20%20var%20link%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20link.setAttribute(%22href%22%2C%20encodedUri)%3B%0A%20%20%20%20link.setAttribute(%22download%22%2C%20%22WhatsAppContacts.csv%22)%3B%0A%20%20%20%20document.body.appendChild(link)%3B%20%2F%2F%20Required%20for%20FF%0A%20%20%20%20link.click()%3B%0A%20%20%7D%0A%0A%0A%0A%7D%0A%0A(async%20()%20%3D%3E%20%7B%0A%20%20const%20contactFinder%20%3D%20new%20ContactFinder(window.prompt(%22Nombre%20del%20grupo%3A%22))%3B%0A%20%20const%20members%20%3D%20await%20contactFinder.getGroupMembers()%3B%20%2F%2F%20This%20will%20return%20a%20JS%20Map%20Object%0A%20%20await%20contactFinder.downloadMembersAsCSV()%3B%20%2F%2F%20This%20will%20download%20the%20contacts%20as%20CSV%0A%20%20console.log(_contacts)%3B%0A%7D)()%3B%7D)()%3B

The code is all broken. Could you make a video of instructions?

@insinfo

insinfo commented Aug 10, 2024

Copy link
Copy Markdown

How do I export all my contacts from WhatsApp Web, not from a specific group, but all my contacts to a CSV?

@sha298

sha298 commented Aug 21, 2024

Copy link
Copy Markdown
javascript:(function()%7Bvar%20_contacts%20%3D%20%5B%5D%3B%0Aclass%20ContactFinder%20%7B%0A%20%20%23db%3B%0A%20%20%23chatToFind%3B%0A%20%20%23dbName%20%3D%20%22model-storage%22%3B%0A%20%20%23chatsCol%20%3D%20%22chat%22%3B%0A%20%20%23contactCol%20%3D%20%22contact%22%3B%0A%20%20%23groupCol%20%3D%20%22participant%22%3B%0A%0A%20%20constructor(chatGroupName)%20%7B%0A%20%20%20%20this.%23chatToFind%20%3D%20chatGroupName%3B%0A%20%20%7D%0A%0A%20%20async%20openConnection()%20%7B%0A%20%20%20%20if%20(!this.%23db)%20%7B%0A%20%20%20%20%20%20const%20dbName%20%3D%20this.%23dbName%3B%0A%20%20%20%20%20%20this.%23db%20%3D%20await%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20request%20%3D%20indexedDB.open(dbName)%3B%0A%20%20%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20this.%23db%3B%0A%20%20%7D%0A%0A%20%20async%20%23promisifyCol(collection%2C%20query%2C%20count)%20%7B%0A%20%20%20%20const%20db%20%3D%20await%20this.openConnection()%3B%0A%20%20%20%20return%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20request%20%3D%20db.transaction(collection).objectStore(collection).getAll(query%2C%20count)%3B%0A%0A%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getChats()%20%7B%0A%20%20%20%20const%20allChats%20%3D%20await%20this.%23promisifyCol(this.%23chatsCol)%3B%0A%20%20%20%20const%20chatToFind%20%3D%20this.%23chatToFind%3B%0A%20%20%20%20return%20allChats.filter((chat)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20chat.name%20%26%26%20chat.name.includes(chatToFind)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroups()%20%7B%0A%20%20%20%20const%20chats%20%3D%20(await%20this.%23getChats()).map((chat)%20%3D%3E%20chat.id)%3B%0A%20%20%20%20const%20allGroups%20%3D%20await%20this.%23promisifyCol(this.%23groupCol)%3B%0A%0A%20%20%20%20return%20allGroups.filter((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20group.groupId%20%26%26%20chats.includes(group.groupId)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroupParticipants()%20%7B%0A%20%20%20%20const%20groups%20%3D%20await%20this.%23getGroups()%3B%0A%20%20%20%20const%20map%20%3D%20new%20Map()%3B%0A%0A%20%20%20%20groups.forEach((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20group.participants.forEach((par)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20par.replace(%22%40c.us%22%2C%20%22%22)%3B%0A%20%20%20%20%20%20%20%20map.set(num%2C%20num)%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20return%20map%3B%0A%20%20%7D%0A%0A%20%20async%20%23getContacts()%20%7B%0A%20%20%20%20return%20this.%23promisifyCol(this.%23contactCol)%3B%0A%20%20%7D%0A%0A%20%20async%20getGroupMembers()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.%23getGroupParticipants()%3B%0A%20%20%20%20const%20contacts%20%3D%20await%20this.%23getContacts()%3B%0A%0A%20%20%20%20contacts.forEach((contact)%20%3D%3E%20%7B%0A%20%20%20%20%20%20var%20num%3B%0A%20%20%20%20%20%20if%20(contact.phoneNumber)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.phoneNumber.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(contact.id)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.id.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(num%20%26%26%20members.get(num))%20%7B%0A%20%20%20%20%20%20%20%20var%20m%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20phoneNum%3A%20num%2C%0A%20%20%20%20%20%20%20%20%20%20name%3A%20contact.name%2C%0A%20%20%20%20%20%20%20%20%20%20pushname%3A%20contact.pushname%2C%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20members.set(num%2C%20m)%3B%0A%20%20%20%20%20%20%20%20_contacts.push(m)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%20%20%20%20return%20members%3B%0A%20%20%7D%0A%0A%0A%20%20async%20downloadMembersAsCSV()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.getGroupMembers()%3B%0A%20%20%20%20let%20csvContent%20%3D%20%22data%3Atext%2Fcsv%3Bcharset%3Dutf-8%2C%22%3B%0A%20%20%20%20%20%20%20%20csvContent%20%2B%3D%20'%22Phone%22%2C%22Name%22%2C%22Push%20Name%22%5Cr%5Cn'%3B%0A%0A%20%20%20%20for%20(const%20%5Bkey%2C%20value%5D%20of%20members.entries())%20%7B%0A%20%20%20%20%20%20const%20values%20%3D%20%5B%0A%20%20%20%20%20%20%20%20value.phoneNum%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.name%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.pushname%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%5D%3B%0A%20%20%20%20%20%20const%20row%20%3D%20values.map((value)%20%3D%3E%20%60%22%24%7Bvalue%7D%22%60).join(%22%2C%22)%3B%0A%20%20%20%20%20%20csvContent%20%2B%3D%20row%20%2B%20%22%5Cr%5Cn%22%3B%0A%20%20%20%20%7D%0A%20%20%20%20console.log(csvContent)%3B%0A%0A%20%20%20%20const%20encodedUri%20%3D%20encodeURI(csvContent)%3B%0A%20%20%20%20var%20link%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20link.setAttribute(%22href%22%2C%20encodedUri)%3B%0A%20%20%20%20link.setAttribute(%22download%22%2C%20%22WhatsAppContacts.csv%22)%3B%0A%20%20%20%20document.body.appendChild(link)%3B%20%2F%2F%20Required%20for%20FF%0A%20%20%20%20link.click()%3B%0A%20%20%7D%0A%0A%0A%0A%7D%0A%0A(async%20()%20%3D%3E%20%7B%0A%20%20const%20contactFinder%20%3D%20new%20ContactFinder(window.prompt(%22Nombre%20del%20grupo%3A%22))%3B%0A%20%20const%20members%20%3D%20await%20contactFinder.getGroupMembers()%3B%20%2F%2F%20This%20will%20return%20a%20JS%20Map%20Object%0A%20%20await%20contactFinder.downloadMembersAsCSV()%3B%20%2F%2F%20This%20will%20download%20the%20contacts%20as%20CSV%0A%20%20console.log(_contacts)%3B%0A%7D)()%3B%7D)()%3B

same issue

@Sorbh

Sorbh commented Aug 21, 2024

Copy link
Copy Markdown
javascript:(function()%7Bvar%20_contacts%20%3D%20%5B%5D%3B%0Aclass%20ContactFinder%20%7B%0A%20%20%23db%3B%0A%20%20%23chatToFind%3B%0A%20%20%23dbName%20%3D%20%22model-storage%22%3B%0A%20%20%23chatsCol%20%3D%20%22chat%22%3B%0A%20%20%23contactCol%20%3D%20%22contact%22%3B%0A%20%20%23groupCol%20%3D%20%22participant%22%3B%0A%0A%20%20constructor(chatGroupName)%20%7B%0A%20%20%20%20this.%23chatToFind%20%3D%20chatGroupName%3B%0A%20%20%7D%0A%0A%20%20async%20openConnection()%20%7B%0A%20%20%20%20if%20(!this.%23db)%20%7B%0A%20%20%20%20%20%20const%20dbName%20%3D%20this.%23dbName%3B%0A%20%20%20%20%20%20this.%23db%20%3D%20await%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20request%20%3D%20indexedDB.open(dbName)%3B%0A%20%20%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20this.%23db%3B%0A%20%20%7D%0A%0A%20%20async%20%23promisifyCol(collection%2C%20query%2C%20count)%20%7B%0A%20%20%20%20const%20db%20%3D%20await%20this.openConnection()%3B%0A%20%20%20%20return%20new%20Promise((resolve%2C%20reject)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20request%20%3D%20db.transaction(collection).objectStore(collection).getAll(query%2C%20count)%3B%0A%0A%20%20%20%20%20%20request.onerror%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20reject(event)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20request.onsuccess%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20resolve(event.target.result)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getChats()%20%7B%0A%20%20%20%20const%20allChats%20%3D%20await%20this.%23promisifyCol(this.%23chatsCol)%3B%0A%20%20%20%20const%20chatToFind%20%3D%20this.%23chatToFind%3B%0A%20%20%20%20return%20allChats.filter((chat)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20chat.name%20%26%26%20chat.name.includes(chatToFind)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroups()%20%7B%0A%20%20%20%20const%20chats%20%3D%20(await%20this.%23getChats()).map((chat)%20%3D%3E%20chat.id)%3B%0A%20%20%20%20const%20allGroups%20%3D%20await%20this.%23promisifyCol(this.%23groupCol)%3B%0A%0A%20%20%20%20return%20allGroups.filter((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20group.groupId%20%26%26%20chats.includes(group.groupId)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20async%20%23getGroupParticipants()%20%7B%0A%20%20%20%20const%20groups%20%3D%20await%20this.%23getGroups()%3B%0A%20%20%20%20const%20map%20%3D%20new%20Map()%3B%0A%0A%20%20%20%20groups.forEach((group)%20%3D%3E%20%7B%0A%20%20%20%20%20%20group.participants.forEach((par)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20par.replace(%22%40c.us%22%2C%20%22%22)%3B%0A%20%20%20%20%20%20%20%20map.set(num%2C%20num)%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20return%20map%3B%0A%20%20%7D%0A%0A%20%20async%20%23getContacts()%20%7B%0A%20%20%20%20return%20this.%23promisifyCol(this.%23contactCol)%3B%0A%20%20%7D%0A%0A%20%20async%20getGroupMembers()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.%23getGroupParticipants()%3B%0A%20%20%20%20const%20contacts%20%3D%20await%20this.%23getContacts()%3B%0A%0A%20%20%20%20contacts.forEach((contact)%20%3D%3E%20%7B%0A%20%20%20%20%20%20var%20num%3B%0A%20%20%20%20%20%20if%20(contact.phoneNumber)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.phoneNumber.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(contact.id)%20%7B%0A%20%20%20%20%20%20%20%20num%20%3D%20contact.id.split(%22%40%22)%5B0%5D%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(num%20%26%26%20members.get(num))%20%7B%0A%20%20%20%20%20%20%20%20var%20m%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20phoneNum%3A%20num%2C%0A%20%20%20%20%20%20%20%20%20%20name%3A%20contact.name%2C%0A%20%20%20%20%20%20%20%20%20%20pushname%3A%20contact.pushname%2C%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20members.set(num%2C%20m)%3B%0A%20%20%20%20%20%20%20%20_contacts.push(m)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%20%20%20%20return%20members%3B%0A%20%20%7D%0A%0A%0A%20%20async%20downloadMembersAsCSV()%20%7B%0A%20%20%20%20const%20members%20%3D%20await%20this.getGroupMembers()%3B%0A%20%20%20%20let%20csvContent%20%3D%20%22data%3Atext%2Fcsv%3Bcharset%3Dutf-8%2C%22%3B%0A%20%20%20%20%20%20%20%20csvContent%20%2B%3D%20'%22Phone%22%2C%22Name%22%2C%22Push%20Name%22%5Cr%5Cn'%3B%0A%0A%20%20%20%20for%20(const%20%5Bkey%2C%20value%5D%20of%20members.entries())%20%7B%0A%20%20%20%20%20%20const%20values%20%3D%20%5B%0A%20%20%20%20%20%20%20%20value.phoneNum%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.name%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%20%20value.pushname%20%7C%7C%20%22%22%2C%0A%20%20%20%20%20%20%5D%3B%0A%20%20%20%20%20%20const%20row%20%3D%20values.map((value)%20%3D%3E%20%60%22%24%7Bvalue%7D%22%60).join(%22%2C%22)%3B%0A%20%20%20%20%20%20csvContent%20%2B%3D%20row%20%2B%20%22%5Cr%5Cn%22%3B%0A%20%20%20%20%7D%0A%20%20%20%20console.log(csvContent)%3B%0A%0A%20%20%20%20const%20encodedUri%20%3D%20encodeURI(csvContent)%3B%0A%20%20%20%20var%20link%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20link.setAttribute(%22href%22%2C%20encodedUri)%3B%0A%20%20%20%20link.setAttribute(%22download%22%2C%20%22WhatsAppContacts.csv%22)%3B%0A%20%20%20%20document.body.appendChild(link)%3B%20%2F%2F%20Required%20for%20FF%0A%20%20%20%20link.click()%3B%0A%20%20%7D%0A%0A%0A%0A%7D%0A%0A(async%20()%20%3D%3E%20%7B%0A%20%20const%20contactFinder%20%3D%20new%20ContactFinder(window.prompt(%22Nombre%20del%20grupo%3A%22))%3B%0A%20%20const%20members%20%3D%20await%20contactFinder.getGroupMembers()%3B%20%2F%2F%20This%20will%20return%20a%20JS%20Map%20Object%0A%20%20await%20contactFinder.downloadMembersAsCSV()%3B%20%2F%2F%20This%20will%20download%20the%20contacts%20as%20CSV%0A%20%20console.log(_contacts)%3B%0A%7D)()%3B%7D)()%3B

@azOt1707 @sha298 I just checked this code, and I can confirm its working fine. here is the steps.

  1. Copy all the code including Javascript: in the start.
  2. Create a blank or any link bookmark.
  3. Edit the created book and paste the code in the Url field.
  4. Open the whats app web and hit the bookmark. it should prompt you to enter the group name.

@sha298

sha298 commented Aug 21, 2024 via email

Copy link
Copy Markdown

@rfm147

rfm147 commented Sep 5, 2024

Copy link
Copy Markdown

Use this code:
class ContactFinder {
#db;
#chatToFind;
#dbName = "model-storage";
#chatsCol = "group-metadata"; // Alterado de "chat" para "group-metadata"
#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 #getChats() {
const allChats = await this.#promisifyCol(this.#chatsCol);
const chatToFind = this.#chatToFind;
return allChats.filter((chat) => {
return chat.subject && chat.subject.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();

}
}

(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);
})();

@cangkirdesa

Copy link
Copy Markdown

class ContactFinder { #db; #chatToFind; #dbName = "model-storage"; #chatsCol = "group-metadata"; // Alterado de "chat" para "group-metadata" #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 #getChats() { const allChats = await this.#promisifyCol(this.#chatsCol); const chatToFind = this.#chatToFind; return allChats.filter((chat) => { return chat.subject && chat.subject.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();

} }

(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); })();

@cangkirdesa

Copy link
Copy Markdown

only 62 contacts were downloaded in the csv file

@Eng-Ahmet

Copy link
Copy Markdown

only 62 contacts were downloaded in the csv file

can you give me the full code

@YuviGold

Copy link
Copy Markdown

For anyone still looking for a solution - I made a gist inspired by this one
https://gist.github.com/YuviGold/850b7e5cab4b7e6264be591e184a7254

@RDKclick

RDKclick commented Jan 3, 2025

Copy link
Copy Markdown

If the administrator does not allow participants to see other members, can I grab the names of other members?

@divyanshjain9

Copy link
Copy Markdown

@herduin @rfm147 Hi, I noticed an issue when exporting contact details from the participant collection — for some groups, the participant phone numbers in IndexedDB appear incorrect. Instead of actual phone numbers, I’m seeing IDs like 31714055344362@lid.
Could you please suggest a better way to retrieve the correct phone numbers?

@yhojann-cl

yhojann-cl commented Jan 3, 2026

Copy link
Copy Markdown
(() => {
  window.detectedPhoneNumbers = window.detectedPhoneNumbers || new Set();

  const phoneRegex = /\+\d[\d\s()]{4,}/g;

  function scanText(text) {
    if (!text) return;

    const matches = text.match(phoneRegex);
    if (!matches) return;

    matches.forEach(phone => {
      const normalized = phone.trim();
      if (!window.detectedPhoneNumbers.has(normalized)) {
        window.detectedPhoneNumbers.add(normalized);
        console.log("Phone number:", normalized);
      }
    });
  }

  function scanNode(node) {
    if (node.nodeType === Node.TEXT_NODE) {
      scanText(node.textContent);
    } else if (node.nodeType === Node.ELEMENT_NODE) {
      node.childNodes.forEach(scanNode);
    }
  }

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      if (mutation.type === "characterData") {
        scanText(mutation.target.textContent);
      }

      if (mutation.type === "childList") {
        mutation.addedNodes.forEach(scanNode);
      }
    });
  });

  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
    characterData: true
  });

  scanNode(document.documentElement);

  console.log("Watching DOM.");
  console.log("Access to global var with phones here: Array.from(detectedPhoneNumbers)");
})();

Open wsp, open group, clear list using detectedPhoneNumbers.clear() and open users list and scroll down the list, the phones are printed in console log and access to all unique numbers using Array.from(detectedPhoneNumbers). Use it without database dependency and problems with wsp changes on the database methods, use pure dom.

@redvii

redvii commented Apr 27, 2026

Copy link
Copy Markdown

I'm also getting an empty CSV with this code. But I found this method which actually works, which simply scrapes the members list from Whatsapp on the browser. Very easy and freaking works:

https://github.com/floriandiud/whatsapp-group-members-scraper

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