fix(newsletter): parse metadata and fetch-messages responses (#2620 port)#503
Conversation
…Sockets#2620 port) Port of upstream PR WhiskeySockets#2620 (@vinikjkkj). Three fixes in one branch — `newsletterMetadata` parse, `newsletterFetchMessages` query+parse, and a duplicated type definition cleanup. ## 1. newsletterMetadata The earlier code cast the raw server response to `NewsletterMetadata` whole, which loses fields in practice: - The wire shape is snake_case nested (`thread_metadata.subscribers_count`, `thread_metadata.picture.direct_path`) while `NewsletterMetadata` is the flat camelCase shape we expose to callers. A direct cast leaves the channel name, subscribers count, etc. as `undefined`. - For preview-only responses (e.g. fetching a non-followed channel via invite), `thread_metadata.picture` is absent and the server returns `image` / `preview` siblings. Reading `.picture` alone returned `undefined` for those cases. `parseNewsletterMetadata` now unwraps the `result` envelope when present, requires a string `id`, and translates the nested response into the flat shape with the documented `picture → image → preview` fallback chain. Fixes upstream issue WhiskeySockets#2204 (channel picture missing). ## 2. newsletterFetchMessages The earlier stanza was: <iq to='<channel-jid>' xmlns='newsletter'> <message_updates count since after/> </iq> That request reaches the server, the server doesn't recognize it, and the request hangs until the caller times out. Upstream issue WhiskeySockets#2555. The correct WA-Web-shaped query is: <iq to='s.whatsapp.net' xmlns='newsletter'> <messages type='jid' jid='<channel-jid>' count='N' [before/after]/> </iq> The XML attribute name flipped from `since` to `before`. The function signature (`(jid, count, since, after)`) is preserved so existing callers keep working — we map `since` to the `before` attribute. The response now comes back as a `<messages>` container with one or more `<message>` children. Each `<message>` carries plaintext-encoded `proto.Message` content plus side counters and metadata. The new `parseFetchedNewsletterMessage` helper unpacks one `<message>` into a structured object: `id`, `serverId`, `type`, `timestamp`, `isSender`, `views`/`forwards`/`responses` counts, edit / original timestamps, media `rcat` (category routing blob), `reactions` array, `pollVotes` array, and the decoded `proto.Message`. This changes the return type from `Promise<BinaryNode>` (effectively opaque, never populated because the request always timed out) to `Promise<Array<…>>`. Since the previous shape was a perpetually-empty timeout, no working caller breaks. ## 3. NewsletterCreateResponse duplicated `src/Types/Newsletter.ts` declared `NewsletterCreateResponse` twice back-to-back with identical bodies. TypeScript silently merges duplicate `interface` declarations so it didn't cause errors, but it broke type search and made future edits risk drifting the two copies. Removed the second declaration. Files: - src/Socket/newsletter.ts: `parseNewsletterMetadata` rewrite, `parseFetchedNewsletterMessage` helper, `newsletterFetchMessages` query+parse rewrite, new imports for `proto`, `BinaryNode`, `getBinaryNodeChildren`, `S_WHATSAPP_NET`. - src/Types/Newsletter.ts: removed duplicated `NewsletterCreateResponse`.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Comment |
|
Thanks for opening this pull request and contributing to the project! The next step is for the maintainers to review your changes. If everything looks good, it will be approved and merged into the main branch. In the meantime, anyone in the community is encouraged to test this pull request and provide feedback. ✅ How to confirm it worksIf you’ve tested this PR, please comment below with: This helps us speed up the review and merge process. 📦 To test this PR locally:If you encounter any issues or have feedback, feel free to comment as well. |
There was a problem hiding this comment.
Pull request overview
Ports upstream fixes for WhatsApp newsletter handling by correcting metadata parsing (including picture fallbacks) and implementing the WA Web <messages> IQ flow so newsletterFetchMessages returns parsed messages instead of timing out. Also cleans up a duplicated type definition.
Changes:
- Fix
parseNewsletterMetadatato unwrap theresultenvelope, requireid, map nested fields, and fall backpicture → image → preview. - Update
newsletterFetchMessagesto querys.whatsapp.netwith<messages type='jid' ...>and parse each<message>into a structured object with decodedproto.Message. - Remove duplicate
NewsletterCreateResponseinterface insrc/Types/Newsletter.ts.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/Socket/newsletter.ts | Adjusts newsletter metadata parsing and implements the new fetch-messages request/response parsing. |
| src/Types/Newsletter.ts | Removes a duplicated NewsletterCreateResponse type declaration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 1. The actual response shape uses snake_case (`thread_metadata. | ||
| // subscribers_count`, `picture.direct_path`) while | ||
| // `NewsletterMetadata` is the flat camelCase shape we expose — | ||
| // casting lost the channel name, subscribers count, etc. |
| const thread = node.thread_metadata ?? {} | ||
| const viewer = node.viewer_metadata ?? {} | ||
| const pic = thread.picture ?? thread.image ?? thread.preview | ||
|
|
||
| return { | ||
| id: node.id, | ||
| name: thread.name?.text ?? '', | ||
| description: thread.description?.text, | ||
| invite: thread.invite, | ||
| creation_time: thread.creation_time ? parseInt(thread.creation_time, 10) : undefined, | ||
| subscribers: thread.subscribers_count ? parseInt(thread.subscribers_count, 10) : undefined, | ||
| picture: pic ? { id: pic.id, directPath: pic.direct_path } : undefined, | ||
| verification: thread.verification, | ||
| mute_state: viewer.mute | ||
| } |
| const reactionsNode = getBinaryNodeChild(node, 'reactions') | ||
| const reactions = reactionsNode | ||
| ? getBinaryNodeChildren(reactionsNode, 'reaction').map(r => ({ | ||
| code: r.attrs.code, | ||
| count: r.attrs.count ? parseInt(r.attrs.count, 10) : 0 | ||
| })) | ||
| : [] | ||
|
|
||
| const votesNode = getBinaryNodeChild(node, 'votes') | ||
| const pollVotes = votesNode | ||
| ? getBinaryNodeChildren(votesNode, 'vote').map(v => ({ | ||
| count: v.attrs.count ? parseInt(v.attrs.count, 10) : 0, | ||
| hash: v.content instanceof Uint8Array ? v.content : undefined | ||
| })) | ||
| : [] |
…a picture fallback PR #508 review caught an ambiguity in the picture-fallback chain inherited from upstream PR WhiskeySockets#2620 (#503 port). The original chain only looked inside `thread_metadata`: const pic = thread.picture ?? thread.image ?? thread.preview but the in-code comment noted that for preview-only responses (non-followed channel via invite) the server returns `image` / `preview` SIBLINGS of `thread_metadata` rather than children of it. The chain was missing the sibling case, so the picture came back undefined for that flow. Extend the fallback to also try `node.image` and `node.preview`. Additive and defensive — `undefined ?? undefined` short-circuits cleanly when the server returns the followed-channel shape (which has worked since #503 merged). Also refine the comment so the chain is unambiguous on the next read. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…a picture fallback PR #508 review caught an ambiguity in the picture-fallback chain inherited from upstream PR WhiskeySockets#2620 (#503 port). The original chain only looked inside `thread_metadata`: const pic = thread.picture ?? thread.image ?? thread.preview but the in-code comment noted that for preview-only responses (non-followed channel via invite) the server returns `image` / `preview` SIBLINGS of `thread_metadata` rather than children of it. The chain was missing the sibling case, so the picture came back undefined for that flow. Extend the fallback to also try `node.image` and `node.preview`. Additive and defensive — `undefined ?? undefined` short-circuits cleanly when the server returns the followed-channel shape (which has worked since #503 merged). Also refine the comment so the chain is unambiguous on the next read. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Port of upstream WhiskeySockets#2620 by @vinikjkkj. Three independent fixes batched together.
1. `newsletterMetadata` cast bruto perdia campos
Antes: `parseNewsletterMetadata` fazia `result as NewsletterMetadata` direto. O wire é snake_case nested (`thread_metadata.subscribers_count`, `thread_metadata.picture.direct_path`) e o tipo exposto é flat camelCase — o cast deixava `name`, `subscribers`, etc. como `undefined`.
Pior: para fetch de canais não-seguidos via invite, o servidor não retorna `thread_metadata.picture` — retorna `image` ou `preview` como siblings. Ler `.picture` direto trazia `undefined`.
Fix: unwrap do envelope `result` quando presente, exige `id` string, traduz para o shape flat com fallback `picture → image → preview` para o thumbnail. Resolve upstream issue WhiskeySockets#2204.
2. `newsletterFetchMessages` timed out
Stanza antiga:
```xml
<message_updates count since after/>
```
O servidor ignora — o request fica pendurado até o caller dar timeout. Resolve upstream issue WhiskeySockets#2555.
Stanza correta (capturada do WA Web):
```xml
<messages type='jid' jid='' count='N' [before/after]/>
```
O atributo de cursor mudou de `since` para `before`; a assinatura da função `(jid, count, since, after)` foi preservada (mapeamos `since` → `before` internamente). Resposta vem em `` com filhos ``; cada um é desempacotado por `parseFetchedNewsletterMessage` em um objeto estruturado:
`id`, `serverId`, `type`, `timestamp`, `isSender`, `views` / `forwards` / `responses`, `editTimestamp`, `originalTimestamp`, `mediaRcat`, `reactions[]`, `pollVotes[]` e o `proto.Message` decodificado do `<plaintext>`.
Mudança de retorno: era `Promise` (que ninguém usava funcional porque o request sempre dava timeout) → agora é `Promise<Array<…>>`. Nenhum caller funcional quebra.
3. `NewsletterCreateResponse` duplicada em `Types/Newsletter.ts`
Duas declarações `export interface NewsletterCreateResponse` consecutivas com corpos idênticos. TypeScript faz merge silencioso então não dava erro, mas o segundo bloco é puro lixo. Removido.
Por que isso não foi pego pela nossa investigação Frida
Nosso hook Frida capturou o cliente oficial do WhatsApp Android — viu como o servidor responde, mas não viu como o Baileys parseia. Esses três bugs vivem em código TypeScript do Baileys:
@vinikjkkj provavelmente bateu de cara com o timeout / dados vazios rodando Baileys.
Test plan
Summary by cubic
Fixes newsletter metadata parsing and message fetching so fields are returned correctly and requests no longer time out. Also removes a duplicate
NewsletterCreateResponsetype.Bug Fixes
resultenvelope, map nested snake_case to the flat camelCase shape, and fall backpicture → image → preview. Requiresidto be a string.<messages type='jid' jid='<channel-jid>' count='N' [before/after]/>tos.whatsapp.net), mapsincetobefore, and parse each<message>into a structured object with counters, reactions, poll votes, and decodedproto.Message.NewsletterCreateResponseinterface.Migration
newsletterFetchMessagesnow returnsPromise<Array<…>>instead ofPromise<BinaryNode>. Update callers to handle an array of parsed messages.Written for commit f9c11d2. Summary will update on new commits.