pedroslopez / whatsapp-web.js

A WhatsApp client library for NodeJS that connects through the WhatsApp Web browser app
https://wwebjs.dev
Apache License 2.0
15.21k stars 3.63k forks source link

Bug on client.getChats() #386

Closed pauloboc closed 3 years ago

pauloboc commented 3 years ago
client.on('ready', () => {
  getUnreadMsg(client);
});

async function getUnreadMsg(client) {
  const allChats = await client.getChats();

  console.log(allChats);
}
(node:6052) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'map' of undefined
    at Client.getChats (C:\Server\src\Client.js:485:22)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async getUnreadMsg (C:\Server\run\server.js:346:20)
(node:6052) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:6052) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
pedroslopez commented 3 years ago

Do you have any chats open with verified businesses, by any chance?

pauloboc commented 3 years ago

No, I took the test with 1 chat and 2 groups.

Teste with Whatsapp personal, not the Wpp businesses

Nurutomo commented 3 years ago

try adding try { } catch (e) { } inside async function

like this

client.on('ready', () => {
  getUnreadMsg(client);
});

async function getUnreadMsg(client) {
  try {
    const allChats = await client.getChats();

    console.log(allChats);
  } catch (e) {
    console.error(e);
  }
}
pauloboc commented 3 years ago

try adding try { } catch (e) { } inside async function

like this

client.on('ready', () => {
  getUnreadMsg(client);
});

async function getUnreadMsg(client) {
  try {
    const allChats = await client.getChats();

    console.log(allChats);
  } catch (e) {
    console.error(e);
  }
}

@Nurutomo TypeError: Cannot read property 'map' of undefined

yuvalfis commented 3 years ago

I'm also experiencing this bug. If I'm executing: await window.WWebJS.getChats(); On Chromium console, it does find the chats.

stefanfuchs commented 3 years ago

I've made a PR some time ago that would probably fix this behaviour. #304

But the problem with that solution was that some people would be already doing a try-catch, so adding a null check in the functions would introduce some breaking changes. Anyway, the PR was closed due to inactivity and I decided in the end to do my own fork which added those changes from the PR.

stefanfuchs commented 3 years ago

Note: I do not encourage anyone else to use my fork, because I can't guarantee it will be updated regularly. I'm just using it in a personal project.

yuvalfis commented 3 years ago

Thanks for responding, But the question is why we get this undefine although await window.WWebJS.getChats(); works when I run it in Chromium console. Is it something with puppeteer?

pedroslopez commented 3 years ago

I don't think @stefanfuchs PR would fix this issue. If it works in chromium, but is returning undefined when being called from puppeteer it mean that some Chats have a property that can't be properly serialized and sent through puppeteer.

This means that one of those chats has some special case that I haven't encountered before. Usually people have resolved It by deleting the specific chat in question, but this isn't ideal and I would like to find the root cause of the issue. If you guys could identify which of your chats is having the issue and maybe even what the difference is, that would be greatly appreciated. I can't find out myself since none of my chats have the issue.

I know of one case where the chat from a verified business account, using the WhatsApp api, from Brazil. I don't know what caused it specifically there.

To track down which chat is having the issue I would try to get each chat, one by one, and see which one has a problem being sent. I wrote this code snippet to find it back then:

        const chatIds = await client.pupPage.evaluate(() => {
            return window.WWebJS.getChats().map(c => c.id._serialized);
        });

        let chatCount = 0;
        for (let chatId of chatIds) {
            const chat = await client.getChatById(chatId);
            if (!chat) {
                console.log(`Could not serialize chat ${chatId}`);
            } else {
                chatCount++;
            }
        }
        console.log(`Correctly got ${chatCount} chats`);

Please, if you find which chat has the issue try to identify it and any special things it may have so we can get to a proper solution that's not just deleting the chat.

yuvalfis commented 3 years ago

Ok, I managed to get all chats by using JSON.stringify()

async function getAllChats(client: Client): Promise<Chat[]> {
    // @ts-ignore
    const chats =await client.pupPage.evaluate(async () => {
        // @ts-ignore
        const chats = await window.WWebJS.getChats();
        return JSON.stringify(chats)
    })
    return JSON.parse(chats).map((chat: any) => ChatFactory.create(client, chat));
}

But then I got an array of Chats with field id as a string instead of ChatId object, same as groupMetadata.participants.[0].id. It same for me all chats.

Maybe WhatsApp changed the structure of Ids?

pedroslopez commented 3 years ago

@yuvalfis can you try out the code snippet I posted above? JSON serialization has a different structure, and formats IDs this way.

yuvalfis commented 3 years ago

@pedroslopez Yes. I changed it a bit :

    const chatIds = await whatsappClient.pupPage.evaluate(async () => {
        const chats = await window.WWebJS.getChats();
        return chats.map(c => c.id._serialized);
    });

    let chatCount = 0;
    for (let chatId of chatIds) {
        try {
            const chat = await whatsappClient.getChatById(chatId);
            if (!chat) {
                console.log(`Could not serialize chat ${chatId}`);
            } else {
                chatCount++;
            }
        } catch (e) {
            console.log(`Could not serialize chat ${chatId}`, e);
        }
    }
    console.log(`Correctly got ${chatCount} chats`);

and got:

Could not serialize chat 13473313545@c.us TypeError: Cannot read property 'isGroup' of undefined
    at Function.create (/Users/yuvalfishler/dev/whatsapp-manger/node_modules/whatsapp-web.js/src/factories/ChatFactory.js:8:17)
    at Client.getChatById (/Users/yuvalfishler/dev/whatsapp-manger/node_modules/whatsapp-web.js/src/Client.js:498:28)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at Object.getContext (/Users/yuvalfishler/dev/whatsapp-manger/src/server/getContext.ts:27:26)
    at Client.<anonymous> (/Users/yuvalfishler/dev/whatsapp-manger/src/server/whatsapp-server.ts:23:18)
Correctly got 780 chats

chat id 13473313545@c.us -> it is a business account powered by a bot and sends messages as a template with buttons (WhatsappAPI)

thats how it looks like on Chromium console when I print it:

0:
archive: false
changeNumberNewJid: undefined
changeNumberOldJid: undefined
ephemeralDuration: 0
ephemeralSettingTimestamp: 0
formattedTitle: "Luke"
id: e {server: "c.us", user: "13473313545", _serialized: "13473313545@c.us"}
isAnnounceGrpRestrict: undefined
isGroup: false
isMuted: false
isReadOnly: false
labels: undefined
lastReceivedKey: e {fromMe: false, remote: e, id: "9D666B365267BA7080", _serialized: "false_13473313545@c.us_9D666B365267BA7080"}
modifyTag: 872101
msgUnsyncedButtonReplyMsgs: undefined
msgs: (5) [{…}, {…}, {…}, {…}, {…}]
muteExpiration: 0
name: "Luke"
notSpam: true
pendingMsgs: false
pin: 0
t: 1603369374
unreadCount: 0
__proto__: Object
length: 1
__proto__: Array(0
pedroslopez commented 3 years ago

@yuvalfis Great! Could you try to identify which property is having the issue?

The function that takes care of serializing the chat is this: https://github.com/pedroslopez/whatsapp-web.js/blob/3d06babed9b8ea830d63b19f0692a3531310d2f4/src/util/Injected.js#L175-L184

Try eliminating properties that contain data that's unserializable. Some candidates that come to mind could be .msgUnsyncedButtonReplyMsgs or if maybe messages are the issue, .msgs.

To remove a property, you can edit that function above and just do something like

delete res.msgUnsyncedButtonReplyMsgs

If messages are indeed the problem (maybe because of the buttons) we may have to do the same thing, getting the messages for the chat and seeing which message has the issue, identify which property has the problem and correct it or remove it from the serialization function (window.WWebJS.getMessageModel() in this case)

yuvalfis commented 3 years ago

@pedroslopez It looks the problem is in .msgs But I didn't manage to find window.WWebJS.getMessageModel() function, any idea?

pedroslopez commented 3 years ago

@pedroslopez It looks the problem is in .msgs

But I didn't manage to find window.WWebJS.getMessageModel() function, any idea?

@yuvalfis what version of the library is it? It was introduced in a recent version to fix another issue

yuvalfis commented 3 years ago

@pedroslopez
"whatsapp-web.js": "^1.10.0",

pedroslopez commented 3 years ago

@pedroslopez

"whatsapp-web.js": "^1.10.0",

@yuvalfis weird, should be there. Anyway, it's right here:

https://github.com/pedroslopez/whatsapp-web.js/blob/3d06babed9b8ea830d63b19f0692a3531310d2f4/src/util/Injected.js#L169-L173

yuvalfis commented 3 years ago

@pedroslopez ok I think I found a solution: I added to window.WWebJS.getChatModel :

        if (res.msgs.length) {
            res.msgs = res.msgs.map(msg => msg.buttons ? msg.buttons.serialize() : msg);
        }

now it looks like:

    window.WWebJS.getChatModel = async chat => {
        let res = chat.serialize();
        res.isGroup = chat.isGroup;
        res.formattedTitle = chat.formattedTitle;
        res.isMuted = chat.mute && chat.mute.isMuted;

        if (chat.groupMetadata) {
            await window.Store.GroupMetadata.update(chat.id._serialized);
            res.groupMetadata = chat.groupMetadata.serialize();
        }
        if (res.msgs.length) {
            res.msgs = res.msgs.map(msg => msg.buttons ? msg.buttons.serialize() : msg);
        }

        return res;
    };

Let me know what you think.

yuvalfis commented 3 years ago

@pedroslopez Mabye should add it here as well:

    window.WWebJS.getMessageModel = message => {
        const msg = message.serialize();
        if (msg.buttons) {
            msg.buttons = msg.buttons.serialize();
        }
        delete msg.pendingAckUpdate;
        return msg;
    };
pedroslopez commented 3 years ago

@yuvalfis We don't really use the .msgs property on chats, so we could just get rid of it (delete res.msgs on the serializer function`.

The change in the getMessageModel function seems good. Can you confirm serializing buttons in this way solves the issue? Please post the result of msg.buttons and msg.buttons.serialize() here.

Also, could you try to fetchMessages() on the chat with the issue before and after applying your fix? Seems like it will work, just want to confirm.

If all this works, please create a pull request with your changes so I can test and merge.

yuvalfis commented 3 years ago

Hi, Ok, no problem. here is console.log before buttons.serialize() and after: Before serializing msg.buttons :

t {_models: Array(1), _index: {…}, _inflight: {…}, modelClass: ƒ, _comparator: ƒ, …}
modelClass: ƒ t()
_cachePolicy: t {_collection: t, _id: "none"}
_comparator: ƒ (e,t)
_index: {0: t}
_inflight: {}
_models: [t]
_resumeOnAvailable: false
_staleCollection: false
isCollection: (...)
length: (...)
models: (...)
__proto__: t
VM62 __puppeteer_evaluation_script__:145 

After serializing msg.buttons : [{…}] 0: displayText: "Open listings" id: "0" phoneNumber: undefined selectionId: "https://www.iamluke.us/" subtype: "quick_reply" url: undefined proto: Object length: 1 proto: Array(0)

yuvalfis commented 3 years ago

@pedroslopez when I trying to push a new branch into Github I get this error:

$ git push origin fix/templete-buttons-serialize

ERROR: Permission to pedroslopez/whatsapp-web.js.git denied to yuvalfis.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

Any idea why?

pedroslopez commented 3 years ago

@pedroslopez when I trying to push a new branch into Github I get this error:


$ git push origin fix/templete-buttons-serialize

ERROR: Permission to pedroslopez/whatsapp-web.js.git denied to yuvalfis.

fatal: Could not read from remote repository.

Please make sure you have the correct access rights

and the repository exists.

Any idea why?

@yuvalfis you need to fork the repository first, make your changes in your fork, and then create a PR to this repository

yuvalfis commented 3 years ago

@pedroslopez PR: https://github.com/pedroslopez/whatsapp-web.js/pull/402