diff --git a/authentication/qr-code.mdx b/authentication/qr-code.mdx index b90de85..2ca94b5 100644 --- a/authentication/qr-code.mdx +++ b/authentication/qr-code.mdx @@ -6,7 +6,7 @@ description: "Authenticate your Baileys socket by scanning a QR code in the What QR code authentication is the default way to link Baileys to your WhatsApp account. Baileys emits a QR string on the `connection.update` event, which you render however you like — to the terminal, to an image, or to your frontend. Open WhatsApp on your phone, navigate to **Linked Devices**, and scan the code to complete the link. - The `printQRInTerminal` socket option is **deprecated**. Listen for the `qr` field on the `connection.update` event and render the code yourself. + The `printQRInTerminal` socket option is **deprecated** and will be removed in a future version. Listen for the `qr` field on the `connection.update` event and render the code yourself. ## Basic setup @@ -49,45 +49,9 @@ QR code authentication is the default way to link Baileys to your WhatsApp accou -## Customizing the browser identity +## Customizing the browser identity and receiving full history -When you connect with a QR code, WhatsApp sees your client as a browser. You can control how it identifies itself using the `Browsers` constant exported from Baileys. This affects the name shown in WhatsApp's **Linked Devices** list. - -```typescript -import makeWASocket, { Browsers } from '@whiskeysockets/baileys' - -// Show as "Ubuntu — My App" -const sock = makeWASocket({ - browser: Browsers.ubuntu('My App'), -}) -``` - -A few ready-made presets are available: - -| Preset | Example | -| --- | --- | -| `Browsers.ubuntu('My App')` | Ubuntu — My App | -| `Browsers.macOS('Desktop')` | macOS — Desktop | -| `Browsers.windows('My App')` | Windows — My App | - -## Receiving full message history - -By default, Baileys connects with a Chrome browser profile, which limits how much message history WhatsApp delivers on the initial sync. To request the full history, set `syncFullHistory: true` and use the `Browsers.macOS('Desktop')` preset, which WhatsApp treats as a desktop client eligible for extended history. - -```typescript -import makeWASocket, { Browsers } from '@whiskeysockets/baileys' - -const sock = makeWASocket({ - browser: Browsers.macOS('Desktop'), - syncFullHistory: true, -}) -``` - -History messages arrive asynchronously via the `messaging-history.set` event after the connection opens. - - - Requesting full history can significantly increase startup time and memory usage on accounts with large chat histories. - +The browser identity Baileys presents to WhatsApp affects how your client appears in **Linked Devices** and how much message history is delivered on first sync. Both options are configured via the socket — see [Configure the Baileys socket connection](/concepts/socket-config) for the `Browsers` presets and the `syncFullHistory` flag. ## Keeping the connection alive diff --git a/concepts/jids.mdx b/concepts/jids.mdx index 627dcc9..4ec5145 100644 --- a/concepts/jids.mdx +++ b/concepts/jids.mdx @@ -1,18 +1,30 @@ --- title: "WhatsApp JIDs explained" -description: "JIDs (Jabber IDs) are how WhatsApp identifies users, groups, and broadcasts in Baileys. Learn the formats and helper functions for working with JIDs." +description: "JIDs (Jabber IDs) are how WhatsApp identifies users, groups, and broadcasts in Baileys. Learn the formats, the PN/LID duality, and helper functions for working with JIDs." --- WhatsApp identifies every participant — users, groups, broadcast lists, and status feeds — with a **JID** (Jabber ID). JIDs originated in the XMPP protocol and follow the format `local@server`. You will encounter them constantly in Baileys: as the target of `sock.sendMessage`, in the `key.remoteJid` of every incoming message, and as parameters to group and contact queries. +Modern WhatsApp identifies the same person in two different ways depending on context. Both are JIDs: + +- **PNJID — Phone Number Jabber Identifier.** Lives on `@s.whatsapp.net` and is derived from the user's phone number. This is the legacy identifier and the one you use when looking someone up by their number. +- **LIDJID — Linked Identity Jabber Identifier.** Lives on `@lid` and is an opaque per-user identifier WhatsApp assigns to anonymize phone numbers in groups, communities, and other shared surfaces. This is the identifier WhatsApp uses by default in Baileys 7.x and later. + +Both forms refer to the same underlying account. WhatsApp lets you resolve a PNJID to its LIDJID (but not the reverse) — see [PNJID ↔ LIDJID resolution](#pnjid--lidjid-resolution) below. + ## JID formats - + `[countrycode][number]@s.whatsapp.net` Example: `19999999999@s.whatsapp.net` + + `[lid]@lid` + + Example: `123456789012345@lid` + `[timestamp]-[random]@g.us` @@ -26,13 +38,28 @@ WhatsApp identifies every participant — users, groups, broadcast lists, and st `status@broadcast` - This is a fixed constant — all status updates go to this JID. + Fixed constant — all status updates go to this JID. + + + `[id]@newsletter` + + Example: `12345@newsletter` +Less common server domains you may see: + +| Server | Meaning | +| --- | --- | +| `@hosted` | Hosted PN — phone-number user routed through Meta hosting | +| `@hosted.lid` | Hosted LID — LID-form user routed through Meta hosting | +| `@bot` | Meta AI / first-party bot account | +| `@c.us` | Legacy WhatsApp server domain (still used for `0@c.us`, the official business JID, and PSA messages) | +| `@call` | Voice/video call signaling | + ### Phone number rules -When constructing a user JID from a phone number: +When constructing a PNJID from a phone number: - Include the country code (e.g., `1` for the US, `44` for the UK). - Do **not** include `+`, `-`, spaces, or parentheses. @@ -55,9 +82,59 @@ if (result?.exists) { --- +## PN ↔ LID: the dual-identity model + +Since 2024, WhatsApp has been migrating from phone-number identifiers to LIDs (Linked Identity JIDs). A LID is an opaque, per-user identifier that hides the underlying phone number — it lets WhatsApp expose your account inside large groups, communities, and channels without leaking your number to other participants. + +In Baileys 7.x and later: + +- New Signal sessions are created in LID form by default. +- A single user has both a PNJID (`...@s.whatsapp.net`) and a LIDJID (`...@lid`). They refer to the same person. +- Group participant fields are typically LIDs; `participantAlt` carries the matching PN, and vice versa. +- `MessageKey.remoteJidAlt` and `MessageKey.participantAlt` give you the alternate identifier for direct messages and group/broadcast/channel messages respectively. +- The `Contact` type now exposes a single `id` plus paired `phoneNumber` (when `id` is a LID) and `lid` (when `id` is a PN). + + + Don't try to "restore" PN JIDs in your application. Migrate your storage, indexing, and routing logic to LIDs — WhatsApp treats LIDs as the canonical identifier going forward. + + +### PNJID ↔ LIDJID resolution + +WhatsApp lets you resolve a phone number to its LID. The reverse — going from a LID back to a phone number — is not generally supported. Use `onWhatsApp` for one-off PN existence checks, and the `lidMapping` store on `sock.signalRepository` for direct conversions: + +```typescript +// PN → LID (single) +const lid = await sock.signalRepository.lidMapping.getLIDForPN( + '19999999999@s.whatsapp.net' +) + +// PN → LID (batch — preferred for bulk lookups) +const mappings = await sock.signalRepository.lidMapping.getLIDsForPNs([ + '19999999999@s.whatsapp.net', + '447911123456@s.whatsapp.net', +]) +// [{ pn: '19999999999@s.whatsapp.net', lid: '...@lid' }, ...] + +// LID → PN (only if Baileys has previously seen the mapping) +const pn = await sock.signalRepository.lidMapping.getPNForLID('123456789012345@lid') +``` + +A `lid-mapping.update` event fires whenever Baileys learns a new PN ↔ LID pair from the wire. For more advanced directory queries (device lists, bulk metadata), see [USync protocol](/advanced/usync). + +### Phone number sharing between accounts + +Because LIDs hide phone numbers by default, WhatsApp provides explicit opt-in flags to exchange them when both sides agree: + +- Businesses can request the recipient's number with `{ requestPhoneNumber: true }` on a sent message. +- Users can share their number with `{ sharePhoneNumber: true }`. + +Businesses have used LIDs since 2023; users were rolled in over the course of 2024. + +--- + ## Multi-device JIDs -In the WhatsApp multi-device protocol, a single phone number can have multiple connected devices. Each device gets a device suffix appended: `19999999999:2@s.whatsapp.net`. The part before the `:` is the user, and the number after is the device ID. +In the WhatsApp multi-device protocol, a single account can have multiple connected devices. Each device gets a device suffix appended to the user portion: `19999999999:2@s.whatsapp.net` or `123456789012345:3@lid`. The part before the `:` is the user, and the number after is the device ID. This is why you must never compare or split JIDs with string operations — a message from device `:0` and a message from device `:2` belong to the same user. @@ -74,7 +151,11 @@ import { jidDecode, jidEncode, jidNormalizedUser } from '@whiskeysockets/baileys // Decode a JID into its components const decoded = jidDecode('19999999999:2@s.whatsapp.net') -// { user: '19999999999', server: 's.whatsapp.net', device: 2 } +// { user: '19999999999', server: 's.whatsapp.net', device: 2, domainType: 0 } + +// Decode a LIDJID +const lidDecoded = jidDecode('123456789012345:3@lid') +// { user: '123456789012345', server: 'lid', device: 3, domainType: 1 } // Normalize a JID — strips device/agent suffix, lowercases const normalized = jidNormalizedUser('19999999999:2@s.whatsapp.net') @@ -85,6 +166,15 @@ const encoded = jidEncode('19999999999', 's.whatsapp.net') // '19999999999@s.whatsapp.net' ``` +The `domainType` field on the decoded result corresponds to the `WAJIDDomains` enum: + +| Value | Constant | Server domain | +| --- | --- | --- | +| `0` | `WHATSAPP` | `s.whatsapp.net` | +| `1` | `LID` | `lid` | +| `128` | `HOSTED` | `hosted` | +| `129` | `HOSTED_LID` | `hosted.lid` | + ### Type checks ```typescript @@ -95,6 +185,9 @@ import { isJidNewsletter, isPnUser, isLidUser, + isHostedPnUser, + isHostedLidUser, + isJidMetaAI, areJidsSameUser, } from '@whiskeysockets/baileys' @@ -102,8 +195,11 @@ isJidGroup('123456789-123345@g.us') // true isJidBroadcast('1234567890@broadcast') // true isJidStatusBroadcast('status@broadcast') // true isJidNewsletter('12345@newsletter') // true -isPnUser('19999999999@s.whatsapp.net') // true -isLidUser('abc123@lid') // true +isPnUser('19999999999@s.whatsapp.net') // true (PNJID) +isLidUser('123456789012345@lid') // true (LIDJID) +isHostedPnUser('19999999999@hosted') // true +isHostedLidUser('123456789012345@hosted.lid') // true +isJidMetaAI('13135550002@bot') // true // Compare the user portion of two JIDs, ignoring device suffix areJidsSameUser( @@ -112,13 +208,28 @@ areJidsSameUser( ) // true ``` + + `areJidsSameUser` compares the user portion only — it does not cross PN/LID boundaries. To check whether a PNJID and a LIDJID refer to the same account, resolve both to the same form first via `getLIDForPN`. + + + + `isJidUser` from earlier Baileys versions has been removed. Use `isPnUser` or `isLidUser` depending on which form you mean. Both PNs and LIDs are JIDs, so the old name was misleading. + + ### Common constants ```typescript -import { STORIES_JID, S_WHATSAPP_NET } from '@whiskeysockets/baileys' +import { + STORIES_JID, + S_WHATSAPP_NET, + OFFICIAL_BIZ_JID, + META_AI_JID, +} from '@whiskeysockets/baileys' -STORIES_JID // 'status@broadcast' -S_WHATSAPP_NET // '@s.whatsapp.net' +STORIES_JID // 'status@broadcast' +S_WHATSAPP_NET // '@s.whatsapp.net' +OFFICIAL_BIZ_JID // '16505361212@c.us' +META_AI_JID // '13135550002@c.us' ``` --- @@ -162,22 +273,34 @@ const sock = makeWASocket({ ```typescript import { areJidsSameUser } from '@whiskeysockets/baileys' -// Works correctly even with device suffixes +// Works correctly with device suffixes — within the same identity form const isSameUser = areJidsSameUser( msg.key.participant, // e.g. '19999999999:3@s.whatsapp.net' sock.user?.id // e.g. '19999999999:0@s.whatsapp.net' ) ``` +### Deduplicating PN and LID for the same user + +```typescript +import { isLidUser } from '@whiskeysockets/baileys' + +async function canonicalLid(jid: string): Promise { + if (isLidUser(jid)) return jid + const lid = await sock.signalRepository.lidMapping.getLIDForPN(jid) + return lid ?? jid // fall back to the original if no mapping exists yet +} +``` + --- ## What not to do - Never split a JID string with `.split('@')` or compare JIDs with `===`. JIDs may carry device suffixes (`:2`), agent fields, or alternate server domains that cause string comparisons to silently produce wrong results. + Never split a JID string with `.split('@')` or compare JIDs with `===`. JIDs may carry device suffixes (`:2`), agent fields, alternate server domains (`@lid`, `@hosted.lid`), or PN/LID duality that cause string comparisons to silently produce wrong results. ```typescript - // Wrong — misses device variants + // Wrong — misses device variants and ignores PN/LID duality const user = jid.split('@')[0] const isSame = jid1 === jid2 diff --git a/concepts/socket-config.mdx b/concepts/socket-config.mdx index 6e46626..e621c26 100644 --- a/concepts/socket-config.mdx +++ b/concepts/socket-config.mdx @@ -39,7 +39,7 @@ sock.ev.on('creds.update', saveCreds) ### `browser` -Controls how Baileys identifies itself to WhatsApp. The type is `WABrowserDescription`, which is a `[platform, browserName, version]` tuple. Use the `Browsers` constant instead of writing the tuple manually. +Controls how Baileys identifies itself to WhatsApp. The type is `WABrowserDescription`, which is a `[platform, browserName, version]` tuple. Use the `Browsers` constant instead of writing the tuple manually — this is also the name that appears in WhatsApp's **Linked Devices** list. ```typescript import makeWASocket, { Browsers } from '@whiskeysockets/baileys' @@ -56,7 +56,35 @@ const sock = makeWASocket({ }) ``` -The browser you choose affects how much history WhatsApp sends on first sync. Desktop browser identities (macOS or Windows) receive more history than mobile ones. +A few ready-made presets are available: + +| Preset | Example | +| --- | --- | +| `Browsers.ubuntu('My App')` | Ubuntu — My App | +| `Browsers.macOS('Desktop')` | macOS — Desktop | +| `Browsers.windows('My App')` | Windows — My App | + +The browser you choose also affects how much history WhatsApp sends on first sync. Desktop identities (macOS or Windows) receive significantly more history than mobile ones. + +### Receiving full message history + +By default, Baileys connects with a Chrome browser profile, which limits how much history WhatsApp delivers on the initial sync. To request the full history, set `syncFullHistory: true` and use the `Browsers.macOS('Desktop')` preset, which WhatsApp treats as a desktop client eligible for extended history. + +```typescript +import makeWASocket, { Browsers } from '@whiskeysockets/baileys' + +const sock = makeWASocket({ + auth: state, + browser: Browsers.macOS('Desktop'), + syncFullHistory: true, +}) +``` + +History messages arrive asynchronously via the `messaging-history.set` event after the connection opens. See [Sync chat history](/advanced/history-sync) for details on consuming the payload. + + + Requesting full history can significantly increase startup time and memory usage on accounts with large chat histories. + ### `logger` diff --git a/docs.json b/docs.json index 43d2d80..a023449 100644 --- a/docs.json +++ b/docs.json @@ -24,77 +24,162 @@ } }, "navigation": { - "tabs": [ + "languages": [ { - "tab": "Documentation", - "groups": [ + "language": "en", + "default": true, + "tabs": [ { - "group": "Get Started", - "pages": [ - "introduction", - "installation", - "quickstart", - "faq" + "tab": "Documentation", + "groups": [ + { + "group": "Get Started", + "pages": [ + "introduction", + "installation", + "quickstart", + "faq" + ] + }, + { + "group": "Authentication", + "pages": [ + "authentication/qr-code", + "authentication/pairing-code", + "authentication/session-management" + ] + }, + { + "group": "Core Concepts", + "pages": [ + "concepts/socket-config", + "concepts/events", + "concepts/jids", + "concepts/data-store" + ] + }, + { + "group": "Messaging", + "pages": [ + "messaging/sending-messages", + "messaging/media-messages", + "messaging/message-actions", + "messaging/chat-management" + ] + }, + { + "group": "Features", + "pages": [ + "features/groups", + "features/privacy", + "features/presence", + "features/broadcasts-stories" + ] + }, + { + "group": "Advanced", + "pages": [ + "advanced/history-sync", + "advanced/usync", + "advanced/calls", + "advanced/custom-functionality", + "advanced/troubleshooting" + ] + }, + { + "group": "Migration", + "pages": [ + "migration/v7", + "migration/v8" + ] + }, + { + "group": "Community", + "pages": [ + "community/contributing", + "community/translations", + "community/sponsor" + ] + } ] - }, - { - "group": "Authentication", - "pages": [ - "authentication/qr-code", - "authentication/pairing-code", - "authentication/session-management" - ] - }, - { - "group": "Core Concepts", - "pages": [ - "concepts/socket-config", - "concepts/events", - "concepts/jids", - "concepts/data-store" - ] - }, - { - "group": "Messaging", - "pages": [ - "messaging/sending-messages", - "messaging/media-messages", - "messaging/message-actions", - "messaging/chat-management" - ] - }, - { - "group": "Features", - "pages": [ - "features/groups", - "features/privacy", - "features/presence", - "features/broadcasts-stories" - ] - }, - { - "group": "Advanced", - "pages": [ - "advanced/history-sync", - "advanced/usync", - "advanced/calls", - "advanced/custom-functionality", - "advanced/troubleshooting" - ] - }, - { - "group": "Migration", - "pages": [ - "migration/v7", - "migration/v8" - ] - }, + } + ] + }, + { + "language": "pt-BR", + "tabs": [ { - "group": "Community", - "pages": [ - "community/contributing", - "community/translations", - "community/sponsor" + "tab": "Documentação", + "groups": [ + { + "group": "Comece aqui", + "pages": [ + "pt-BR/introduction", + "pt-BR/installation", + "pt-BR/quickstart", + "pt-BR/faq" + ] + }, + { + "group": "Autenticação", + "pages": [ + "pt-BR/authentication/qr-code", + "pt-BR/authentication/pairing-code", + "pt-BR/authentication/session-management" + ] + }, + { + "group": "Conceitos principais", + "pages": [ + "pt-BR/concepts/socket-config", + "pt-BR/concepts/events", + "pt-BR/concepts/jids", + "pt-BR/concepts/data-store" + ] + }, + { + "group": "Mensagens", + "pages": [ + "pt-BR/messaging/sending-messages", + "pt-BR/messaging/media-messages", + "pt-BR/messaging/message-actions", + "pt-BR/messaging/chat-management" + ] + }, + { + "group": "Funcionalidades", + "pages": [ + "pt-BR/features/groups", + "pt-BR/features/privacy", + "pt-BR/features/presence", + "pt-BR/features/broadcasts-stories" + ] + }, + { + "group": "Avançado", + "pages": [ + "pt-BR/advanced/history-sync", + "pt-BR/advanced/usync", + "pt-BR/advanced/calls", + "pt-BR/advanced/custom-functionality", + "pt-BR/advanced/troubleshooting" + ] + }, + { + "group": "Migração", + "pages": [ + "pt-BR/migration/v7", + "pt-BR/migration/v8" + ] + }, + { + "group": "Comunidade", + "pages": [ + "pt-BR/community/contributing", + "pt-BR/community/translations", + "pt-BR/community/sponsor" + ] + } ] } ] diff --git a/faq.mdx b/faq.mdx index d018c29..5e15416 100644 --- a/faq.mdx +++ b/faq.mdx @@ -35,7 +35,7 @@ No. It is fine for development and small bots, but it does heavy disk I/O that d ## How do I receive my full chat history? -Set `syncFullHistory: true` and use a desktop browser preset: +Set `syncFullHistory: true` and use a desktop browser preset. Both options are part of the socket config — see [Receiving full message history](/concepts/socket-config#receiving-full-message-history). ```ts import makeWASocket, { Browsers } from '@whiskeysockets/baileys' @@ -50,7 +50,7 @@ WhatsApp delivers the history asynchronously through the `messaging-history.set` ## What is a LID and why do I see them instead of phone numbers? -A LID (Local Identifier) is a per-user, anonymized identifier WhatsApp assigns to each account. By default, all new Signal sessions are LID-based as of Baileys 7.x. You can resolve a phone number (PN) to a LID using `onWhatsApp()`, but not the reverse. Don't try to restore PN JIDs in your application — migrate to LIDs. See [Migrate to Baileys v7](/migration/v7). +A **LIDJID** (Linked Identity Jabber Identifier, on `@lid`) is a per-user, anonymized identifier WhatsApp assigns to each account. The legacy phone-number form is the **PNJID** (Phone Number Jabber Identifier, on `@s.whatsapp.net`). By default, all new Signal sessions are LID-based as of Baileys 7.x. You can resolve a PNJID to its LIDJID via `onWhatsApp()` or `getLIDForPN`, but not generally the reverse. Don't try to restore PN JIDs in your application — migrate to LIDs. See [WhatsApp JIDs explained](/concepts/jids) and [Migrate to Baileys v7](/migration/v7). ## Why am I getting rate-limited or banned when sending to groups? diff --git a/migration/v7.mdx b/migration/v7.mdx index a0ab60c..45204d4 100644 --- a/migration/v7.mdx +++ b/migration/v7.mdx @@ -5,9 +5,9 @@ description: "Breaking changes in Baileys 7.x: LIDs, removed ACKs, ESM-only buil Baileys 7.x introduces several breaking changes you must address when upgrading from 6.x. This guide walks through each one in the order most projects encounter them. -## LIDs (Local Identifiers) +## LIDs (Linked Identity JIDs) -WhatsApp finalized its LID rollout in 2024. A LID is a per-user identifier (rendered as `+43.......21` in the WhatsApp UI) that anonymizes phone numbers in large groups. By default, all new Signal sessions in Baileys 7.x are created in the LID format, and existing sessions are migrated automatically. +WhatsApp finalized its LID rollout in 2024. A **LIDJID** (Linked Identity Jabber Identifier) is a per-user identifier on the `@lid` server that anonymizes phone numbers in large groups, in contrast to the legacy **PNJID** (Phone Number Jabber Identifier) on `@s.whatsapp.net`. By default, all new Signal sessions in Baileys 7.x are created in the LID format, and existing sessions are migrated automatically. See [WhatsApp JIDs explained](/concepts/jids) for the full PN/LID model. The LID system requires your auth state to support the `lid-mapping`, `device-list`, and `tctoken` keys. Check your `SignalDataTypeMap` and update your custom auth implementation before upgrading. diff --git a/pt-BR/advanced/calls.mdx b/pt-BR/advanced/calls.mdx new file mode 100644 index 0000000..f5301dc --- /dev/null +++ b/pt-BR/advanced/calls.mdx @@ -0,0 +1,32 @@ +--- +title: "Lidar com chamadas do WhatsApp" +description: "Escute chamadas recebidas e rejeite-as com o Baileys." +--- + +O Baileys não atende chamadas, mas detecta e rejeita programaticamente. + +## Escutar chamadas + +```typescript +sock.ev.on('call', async (calls) => { + for (const call of calls) { + console.log(`Call ${call.status} from ${call.from}`) + } +}) +``` + +## Rejeitar uma chamada + +```typescript +sock.ev.on('call', async (calls) => { + for (const call of calls) { + if (call.status === 'offer') { + await sock.rejectCall(call.id, call.from) + } + } +}) +``` + + + O Baileys não aceita ou conduz chamadas de voz/vídeo. Rejeitar é a única ação suportada. + diff --git a/pt-BR/advanced/custom-functionality.mdx b/pt-BR/advanced/custom-functionality.mdx new file mode 100644 index 0000000..9e5955b --- /dev/null +++ b/pt-BR/advanced/custom-functionality.mdx @@ -0,0 +1,110 @@ +--- +title: "Estender o Baileys com funcionalidades customizadas" +description: "Registre callbacks brutos do WebSocket, ative debug logging e entenda o protocolo de binary nodes do WhatsApp." +--- + +## Ative debug logging + +```typescript +import makeWASocket from '@whiskeysockets/baileys' +import P from 'pino' + +const sock = makeWASocket({ + logger: P({ level: 'debug' }), +}) +``` + +Você verá entradas assim: + +```json +{ + "level": 10, + "fromMe": false, + "frame": { + "tag": "ib", + "attrs": { + "from": "@s.whatsapp.net" + }, + "content": [ + { + "tag": "edge_routing", + "attrs": {}, + "content": [ + { + "tag": "routing_info", + "attrs": {}, + "content": { + "type": "Buffer", + "data": [8,2,8,5] + } + } + ] + } + ] + }, + "msg":"communication" +} +``` + +## Como o WhatsApp comunica: binary nodes + +| Campo | Descrição | +| --- | --- | +| `tag` | Sobre o que é o frame. | +| `attrs` | Mapa de chave-valor com metadados. | +| `content` | Payload — array de nodes filhos ou `Buffer`. | + +```typescript +import type { BinaryNode } from '@whiskeysockets/baileys' +``` + + + Para a camada criptográfica, estude o protocolo Libsignal e o protocolo Noise. + + +## Registrar callbacks de WebSocket + +### Match por tag + +```typescript +sock.ws.on('CB:edge_routing', (node: BinaryNode) => { }) +``` + +### Match por tag e atributo + +```typescript +sock.ws.on('CB:edge_routing,id:abcd', (node: BinaryNode) => { }) +``` + +### Match por tag, atributo e tag de conteúdo + +```typescript +sock.ws.on('CB:edge_routing,id:abcd,routing_info', (node: BinaryNode) => { }) +``` + +### Exemplo com tipos + +```typescript +import makeWASocket, { type BinaryNode } from '@whiskeysockets/baileys' +import P from 'pino' + +const sock = makeWASocket({ + logger: P({ level: 'debug' }), +}) + +sock.ws.on('CB:edge_routing', (node: BinaryNode) => { + console.log('received edge_routing node', node) +}) +``` + +## Fluxo de eventos + + + Seus callbacks `CB:` rodam antes dos handlers internos, dando acesso a mensagens de protocolo que o Baileys ainda não expõe. + + +## Trabalho avançado de protocolo + + + Modificar fluxos criptográficos pode quebrar entrega silenciosamente. Faça mudanças aqui só com entendimento profundo de Noise e Signal. + diff --git a/pt-BR/advanced/history-sync.mdx b/pt-BR/advanced/history-sync.mdx new file mode 100644 index 0000000..14a5d22 --- /dev/null +++ b/pt-BR/advanced/history-sync.mdx @@ -0,0 +1,48 @@ +--- +title: "Sincronizar histórico de conversa" +description: "Receba chats, contatos e mensagens anteriores via messaging-history.set e peça mais sob demanda com fetchMessageHistory." +--- + +Após conectar, o socket baixa e processa seus chats, contatos e mensagens existentes via `messaging-history.set`. + +## Tratando o payload + +```ts +sock.ev.on('messaging-history.set', ({ + chats: newChats, + contacts: newContacts, + messages: newMessages, + syncType, +}) => { + // Persista no seu store +}) +``` + +## Pedir histórico completo + +```ts +import makeWASocket, { Browsers } from '@whiskeysockets/baileys' + +const sock = makeWASocket({ + browser: Browsers.macOS('Desktop'), + syncFullHistory: true, +}) +``` + + + Sincronização completa aumenta tempo de inicialização e memória em contas grandes. + + +## Desabilitar sincronização + +```ts +const sock = makeWASocket({ + shouldSyncHistoryMessage: () => false, +}) +``` + +## Sincronização sob demanda + +```ts +await sock.fetchMessageHistory(/* count, oldestMsgKey, oldestMsgTimestamp */) +``` diff --git a/pt-BR/advanced/troubleshooting.mdx b/pt-BR/advanced/troubleshooting.mdx new file mode 100644 index 0000000..7f7db72 --- /dev/null +++ b/pt-BR/advanced/troubleshooting.mdx @@ -0,0 +1,229 @@ +--- +title: "Resolver problemas comuns do Baileys" +description: "Diagnostique e corrija problemas: quedas de conexão, falhas de QR, problemas de entrega, expiração de sessão e erros de upload de mídia." +--- + +## Problemas comuns + + + + + O Baileys não reconecta automaticamente — é proposital. Cheque `DisconnectReason.loggedOut` antes de retry. `401` = deslogado ativamente, não adianta retry. + + ```typescript + import makeWASocket, { DisconnectReason, useMultiFileAuthState } from '@whiskeysockets/baileys' + import { Boom } from '@hapi/boom' + + async function connectToWhatsApp () { + const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys') + const sock = makeWASocket({ + auth: state + }) + sock.ev.on('connection.update', (update) => { + const { connection, lastDisconnect } = update + if(connection === 'close') { + const shouldReconnect = (lastDisconnect.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut + if(shouldReconnect) { + connectToWhatsApp() + } + } else if(connection === 'open') { + console.log('opened connection') + } + }) + + sock.ev.on('creds.update', saveCreds) + } + + connectToWhatsApp() + ``` + + + + Se o QR nunca aparece, geralmente já existem credenciais válidas. Para forçar um QR novo, apague a pasta `auth_info_baileys/` e reinicie. + + ```typescript + sock.ev.on('connection.update', (update) => { + console.log('connection update:', update) + }) + ``` + + + `printQRInTerminal` está deprecated. Escute o `'qr'` em `connection.update` e renderize com `qrcode-terminal`. + + + + + Implemente `getMessage` em `SocketConfig`: + + ```typescript + const sock = makeWASocket({ + getMessage: async (key) => await getMessageFromStore(key) + }) + ``` + + Para retries completos: + + ```typescript + import NodeCache from '@cacheable/node-cache' + import { CacheStore } from '@whiskeysockets/baileys' + + const msgRetryCounterCache = new NodeCache() as CacheStore + + const sock = makeWASocket({ + msgRetryCounterCache, + getMessage: async (key) => await getMessageFromStore(key), + }) + ``` + + + + Você precisa de `getMessage` em `SocketConfig` e `getAggregateVotesInPollMessage`. + + ```typescript + import { getAggregateVotesInPollMessage } from '@whiskeysockets/baileys' + + sock.ev.on('messages.update', event => { + for(const { key, update } of event) { + if(update.pollUpdates) { + const pollCreation = await getMessage(key) + if(pollCreation) { + console.log( + 'aggregation: ', + getAggregateVotesInPollMessage({ + message: pollCreation, + pollUpdates: update.pollUpdates, + }) + ) + } + } + } + }) + ``` + + + + Converta com `ffmpeg`: + + ```bash + ffmpeg -i input.mp4 -avoid_negative_ts make_zero -ac 1 output.ogg + ``` + + ```typescript + await sock.sendMessage(jid, { + audio: { url: './output.ogg' }, + mimetype: 'audio/mp4' + }) + ``` + + + + Configure `cachedGroupMetadata`: + + ```typescript + import NodeCache from '@cacheable/node-cache' + + const groupCache = new NodeCache({ stdTTL: 5 * 60, useClones: false }) + + const sock = makeWASocket({ + cachedGroupMetadata: async (jid) => groupCache.get(jid) + }) + + sock.ev.on('groups.update', async ([event]) => { + const metadata = await sock.groupMetadata(event.id) + groupCache.set(event.id, metadata) + }) + + sock.ev.on('group-participants.update', async (event) => { + const metadata = await sock.groupMetadata(event.id) + groupCache.set(event.id, metadata) + }) + ``` + + + + Geralmente por `chatModify` com dados incorretos. + + ```typescript + // ok + await sock.chatModify({ archive: true, lastMessages: [lastMsgInChat] }, jid) + + // perigoso + await sock.chatModify({ archive: true, lastMessages: [] }, jid) + ``` + + + Nunca chame `chatModify` com dados não verificados. + + + + + + + ```bash + rm -rf auth_info_baileys/ + ``` + + + + + ```typescript + sock.ev.on('creds.update', saveCreds) + ``` + + + + + + ```typescript + await sock.updateMediaMessage(msg) + ``` + + Em `downloadMediaMessage`: + + ```typescript + import { createWriteStream } from 'fs' + import { downloadMediaMessage, getContentType } from '@whiskeysockets/baileys' + + sock.ev.on('messages.upsert', async ({ messages }) => { + for (const m of messages) { + if (!m.message) return + const messageType = getContentType(m.message) + + if (messageType === 'imageMessage') { + const stream = await downloadMediaMessage( + m, + 'stream', + { }, + { + logger, + reuploadRequest: sock.updateMediaMessage + } + ) + const writeStream = createWriteStream('./my-download.jpeg') + stream.pipe(writeStream) + } + } + }) + ``` + + + + +## Debug logging completo + +```typescript +import makeWASocket from '@whiskeysockets/baileys' +import P from 'pino' + +const sock = makeWASocket({ + logger: P({ level: 'debug' }), +}) +``` + +Para mais, veja [Estender o Baileys](/pt-BR/advanced/custom-functionality). + +## Suporte + + + Servidor Discord da comunidade. + diff --git a/pt-BR/advanced/usync.mdx b/pt-BR/advanced/usync.mdx new file mode 100644 index 0000000..db60faa --- /dev/null +++ b/pt-BR/advanced/usync.mdx @@ -0,0 +1,36 @@ +--- +title: "Protocolo USync" +description: "Use o protocolo USync (e MEX) para consultar metadados de usuário, mapeamentos LID/PN, listas de aparelhos e outros dados de diretório." +--- + +USync é o protocolo de diretório do WhatsApp. É o que dá poder ao `onWhatsApp()`, à resolução LID/PN, a buscas de listas de aparelhos e consultas similares. + + + O suporte a USync é intencionalmente baixo nível. Se existir um helper de alto nível (`onWhatsApp`, `getLIDsForPNs` etc.), prefira-o. + + +## Quando usar USync + +- Resolver números para LIDs em massa. +- Consultar listas de aparelhos. +- Sondar metadados que o Baileys ainda não envolve. + +## Construindo uma query USync + +Uma query consiste em: + +- Um tipo de query (ex.: lookup de contato ou lista de aparelhos). +- Lista de usuários identificados por JID/PN/LID. +- Os protocolos (subqueries) que você quer de cada usuário. + +Veja `src/WAUSync` no repositório do Baileys para a lista canônica de classes. + +## Queries MEX + +O Baileys também suporta MEX (camada de query estilo GraphQL) para canais, comunidades, catálogos. Procure `executeUSyncQuery` no código para exemplos. + +## Avisos + +- O WhatsApp pode rate-limitar queries USync agressivas. +- Schemas mudam ocasionalmente. +- USync é não documentado externamente — sujeito a mudança sem aviso. diff --git a/pt-BR/authentication/pairing-code.mdx b/pt-BR/authentication/pairing-code.mdx new file mode 100644 index 0000000..382233f --- /dev/null +++ b/pt-BR/authentication/pairing-code.mdx @@ -0,0 +1,115 @@ +--- +title: "Conectar com código de pareamento" +description: "Vincule o Baileys ao WhatsApp Web sem QR code usando um código de pareamento de 8 dígitos digitado no celular." +--- + +A autenticação por código de pareamento permite vincular o Baileys ao WhatsApp usando um código de 8 dígitos em vez de um QR code. Útil em ambientes headless ou quando você quer vincular um aparelho digitando um código no celular. + + + O código de pareamento é um método para conectar ao WhatsApp Web sem escanear um QR code. **Não** é a Mobile API. Você só pode vincular um aparelho por número de telefone com este método. Veja a [FAQ do WhatsApp](https://faq.whatsapp.com/378279804439436). + + +## Configuração + + + + Crie o socket sem renderizar QR code. Você pode simplesmente ignorar o campo `qr` em `connection.update` ao usar códigos de pareamento. + + ```typescript + import makeWASocket from '@whiskeysockets/baileys' + + const sock = makeWASocket({}) + ``` + + + A opção legada `printQRInTerminal` está **obsoleta** e não deve ser definida. + + + + + Verifique `sock.authState.creds.registered` antes de pedir um código. Se o socket já estiver registrado, não precisa solicitar um novo. + + ```typescript + if (!sock.authState.creds.registered) { + const number = '15551234567' + const code = await sock.requestPairingCode(number) + console.log(`Pairing code: ${code}`) + } + ``` + + O `code` retornado é uma string de 8 dígitos que você digita no celular. + + + + No celular, abra o WhatsApp e vá em **Configurações → Aparelhos conectados → Conectar um aparelho**. Selecione **Conectar com número de telefone** e digite o código de 8 dígitos. + + Após confirmar, o evento `connection.update` dispara com `connection: 'open'`. + + + +## Formato do número de telefone + +O número que você passa para `requestPairingCode` deve seguir estas regras: + +- Inclua o código do país (ex.: `1` para EUA, `55` para Brasil) +- Apenas dígitos — sem `+`, `(`, `)` ou `-` +- Sem espaços + + + +```typescript Correto +const code = await sock.requestPairingCode('15551234567') +const code = await sock.requestPairingCode('5511999999999') +``` + +```typescript Incorreto +const code = await sock.requestPairingCode('+1 (555) 123-4567') +const code = await sock.requestPairingCode('555-123-4567') +``` + + + +## Exemplo completo + +O exemplo a seguir usa `readline` para pedir o número em tempo de execução: + +```typescript +import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys' +import readline from 'readline' + +const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) +const question = (text: string) => new Promise((resolve) => rl.question(text, resolve)) + +async function connect() { + const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys') + + const sock = makeWASocket({ + auth: state, + }) + + sock.ev.on('connection.update', async ({ connection, qr }) => { + if (qr && !sock.authState.creds.registered) { + const number = await question('Enter your phone number (digits only, with country code):\n') + const code = await sock.requestPairingCode(number) + console.log(`Pairing code: ${code}`) + } + + if (connection === 'open') { + console.log('Connected to WhatsApp') + rl.close() + } + }) + + sock.ev.on('creds.update', saveCreds) +} + +connect() +``` + + + O campo `qr` em `connection.update` dispara mesmo no modo de pareamento. Use-o como gatilho para chamar `requestPairingCode`. + + + + Combine com `useMultiFileAuthState` para que o código de pareamento só seja necessário uma vez. Veja [Salvar e restaurar sessões do WhatsApp](/pt-BR/authentication/session-management). + diff --git a/pt-BR/authentication/qr-code.mdx b/pt-BR/authentication/qr-code.mdx new file mode 100644 index 0000000..c0895f5 --- /dev/null +++ b/pt-BR/authentication/qr-code.mdx @@ -0,0 +1,92 @@ +--- +title: "Conectar com QR code" +description: "Autentique seu socket Baileys escaneando um QR code no app do WhatsApp. Cobre o evento connection.update e opções de renderização." +--- + +A autenticação por QR code é a forma padrão de vincular o Baileys à sua conta do WhatsApp. O Baileys emite uma string de QR pelo evento `connection.update`, que você pode renderizar como quiser — no terminal, em uma imagem ou no seu frontend. Abra o WhatsApp no celular, vá em **Aparelhos conectados** e escaneie o código para concluir a vinculação. + + + A opção `printQRInTerminal` está **obsoleta** e será removida numa versão futura. Escute o campo `qr` em `connection.update` e renderize o código você mesmo. + + +## Configuração básica + + + + Se ainda não fez isso, adicione o Baileys ao seu projeto. + + ```bash + npm install @whiskeysockets/baileys qrcode-terminal + ``` + + + + A string de QR é entregue pelo evento `connection.update`. Use [`qrcode-terminal`](https://www.npmjs.com/package/qrcode-terminal) (ou [`qrcode`](https://www.npmjs.com/package/qrcode) para imagem/canvas). + + ```typescript + import makeWASocket from '@whiskeysockets/baileys' + import qrcode from 'qrcode-terminal' + + const sock = makeWASocket({}) + + sock.ev.on('connection.update', ({ connection, qr }) => { + if (qr) { + qrcode.generate(qr, { small: true }) + } + + if (connection === 'open') { + console.log('Connected to WhatsApp') + } + }) + ``` + + Em produção, envie a string `qr` para o seu frontend e renderize lá em vez do terminal. + + + + No seu celular, abra o WhatsApp e vá em **Configurações → Aparelhos conectados → Conectar um aparelho**. Aponte a câmera para o QR renderizado. + + Após escanear, o WhatsApp **força a desconexão** do socket para que o Baileys reconecte com credenciais completas. Trate-o reconectando em `DisconnectReason.restartRequired` (veja [Mantendo a conexão ativa](#mantendo-a-conexão-ativa)). + + + +## Personalizar a identidade do navegador e receber o histórico completo + +A identidade de navegador que o Baileys apresenta ao WhatsApp afeta como seu cliente aparece em **Aparelhos conectados** e quanto histórico de mensagens é entregue na primeira sincronização. Ambas são configuradas pelo socket — veja [Configurar a conexão do Baileys](/pt-BR/concepts/socket-config) para os presets `Browsers` e a flag `syncFullHistory`. + +## Mantendo a conexão ativa + +O Baileys mantém uma conexão WebSocket persistente. Se o seu processo encerrar, a sessão é perdida. Trate o evento `connection.update` para detectar desconexões e reconectar quando apropriado. + +```typescript +import makeWASocket, { DisconnectReason } from '@whiskeysockets/baileys' +import { Boom } from '@hapi/boom' +import qrcode from 'qrcode-terminal' + +function connect() { + const sock = makeWASocket({}) + + sock.ev.on('connection.update', ({ connection, lastDisconnect, qr }) => { + if (qr) { + qrcode.generate(qr, { small: true }) + } + + if (connection === 'close') { + const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode + const shouldReconnect = statusCode !== DisconnectReason.loggedOut + + if (shouldReconnect) { + connect() + } else { + console.log('Logged out. Re-scan the QR code to reconnect.') + } + } + }) +} + +connect() +``` + + + Combine a autenticação por QR code com `useMultiFileAuthState` para que você só precise escanear uma vez. Veja [Salvar e restaurar sessões do WhatsApp](/pt-BR/authentication/session-management). + diff --git a/pt-BR/authentication/session-management.mdx b/pt-BR/authentication/session-management.mdx new file mode 100644 index 0000000..df3051a --- /dev/null +++ b/pt-BR/authentication/session-management.mdx @@ -0,0 +1,147 @@ +--- +title: "Salvar e restaurar sessões do WhatsApp" +description: "Persista as credenciais de autenticação do Baileys em disco para nunca precisar reescanear o QR. Cobre useMultiFileAuthState e padrões customizados." +--- + +Por padrão, o Baileys mantém suas credenciais de autenticação apenas em memória. Quando o processo reinicia, a sessão se perde e você precisa escanear o QR de novo. Persistir o estado de autenticação resolve isso. + +## `useMultiFileAuthState` + +`useMultiFileAuthState` é o utilitário interno para persistir sessão em arquivos. Ele guarda credenciais e chaves de sessão Signal como arquivos JSON dentro de uma pasta. + +```typescript +import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys' + +const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys') + +const sock = makeWASocket({ + auth: state, +}) + +sock.ev.on('connection.update', ({ qr }) => { + if (qr) console.log('QR:', qr) +}) + +sock.ev.on('creds.update', saveCreds) +``` + +## Como o estado de autenticação é estruturado + +O objeto `state` está em conformidade com o tipo `AuthenticationState`: + +```typescript +type AuthenticationState = { + creds: AuthenticationCreds + keys: SignalKeyStore +} +``` + +Ambas devem ser salvas e restauradas juntas. Perder uma quebra a sessão. + +### Salvando chaves do Signal + + + Toda vez que uma mensagem é enviada ou recebida, o Baileys pode atualizar as chaves de sessão Signal armazenadas em `authState.keys`. Se você não salvar essas atualizações, mensagens vão falhar para destinatários cujas sessões foram rotacionadas. Sempre escute `creds.update` e chame sua função de salvar prontamente. + + +## Melhorando a performance com `makeCacheableSignalKeyStore` + +Em produção, toda mensagem dispara consultas a chaves Signal que tocam o disco. Envolver seu key store com `makeCacheableSignalKeyStore` adiciona uma camada de cache em memória. + +```typescript +import makeWASocket, { + useMultiFileAuthState, + makeCacheableSignalKeyStore, +} from '@whiskeysockets/baileys' +import P from 'pino' + +const logger = P({ level: 'silent' }) +const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys') + +const sock = makeWASocket({ + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, logger), + }, +}) + +sock.ev.on('creds.update', saveCreds) +``` + +## Exemplo completo de reconexão + +```typescript +import makeWASocket, { + DisconnectReason, + useMultiFileAuthState, + makeCacheableSignalKeyStore, +} from '@whiskeysockets/baileys' +import { Boom } from '@hapi/boom' +import P from 'pino' + +const logger = P({ level: 'silent' }) + +async function connectToWhatsApp() { + const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys') + + const sock = makeWASocket({ + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, logger), + }, + }) + + sock.ev.on('connection.update', ({ connection, lastDisconnect }) => { + if (connection === 'close') { + const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode + const shouldReconnect = statusCode !== DisconnectReason.loggedOut + + if (shouldReconnect) { + connectToWhatsApp() + } else { + console.log('Logged out. Delete the auth folder and re-scan to reconnect.') + } + } else if (connection === 'open') { + console.log('Connected to WhatsApp') + } + }) + + sock.ev.on('creds.update', saveCreds) +} + +connectToWhatsApp() +``` + +## Serialização JSON confiável com `BufferJSON` + +A implementação de `useMultiFileAuthState` serializa credenciais usando `BufferJSON`, um utilitário que lida com `Buffer` e `Uint8Array` durante `JSON.stringify` / `JSON.parse`. + +```typescript +import { BufferJSON } from '@whiskeysockets/baileys' + +const serialized = JSON.stringify(creds, BufferJSON.replacer) +const creds = JSON.parse(serialized, BufferJSON.reviver) +``` + +## Construindo um auth store em banco de dados + +Para produção, implemente um `AuthenticationState` respaldado por banco. Sua implementação deve satisfazer: + +```typescript +type SignalKeyStore = { + get( + type: T, + ids: string[] + ): Promise<{ [id: string]: SignalDataTypeMap[T] }> + set(data: SignalDataSet): Promise + clear?(): Promise +} +``` + + + Estude como `useMultiFileAuthState` funciona — a implementação em arquivos espelha a interface que seu store de banco precisa satisfazer. + + + + Nunca commite sua pasta de auth ou credenciais de banco. Os arquivos em `auth_info_baileys/` contêm chaves Signal de longa duração equivalentes a uma chave SSH privada. + diff --git a/pt-BR/community/contributing.mdx b/pt-BR/community/contributing.mdx new file mode 100644 index 0000000..23e0689 --- /dev/null +++ b/pt-BR/community/contributing.mdx @@ -0,0 +1,33 @@ +--- +title: "Contribuindo para o Baileys" +description: "Como contribuir com código, documentação e traduções para o Baileys." +--- + +O Baileys é mantido pela comunidade. Contribuições de todos os tamanhos são bem-vindas. + +## Contribuindo com código + +1. Faça fork de [WhiskeySockets/Baileys](https://github.com/WhiskeySockets/Baileys) e crie uma branch a partir de `master`. +2. Rode com Yarn 4 (via `corepack`). É só ESM — veja [Migrar para o Baileys v7](/pt-BR/migration/v7). +3. Adicione/atualize testes onde fizer sentido. +4. Abra um pull request. + +Para mudanças grandes, abra uma discussão antes. + +## Contribuindo para a documentação + +A doc fica em [WhiskeySockets/docs](https://github.com/WhiskeySockets/docs). Edite os `.mdx` e abra um PR. + +Para páginas novas: voz na segunda pessoa, headings em sentence-case, tags de linguagem em blocos de código, links relativos. + +## Traduções + +Veja [Traduções](/pt-BR/community/translations). + +## Estendendo o Baileys + +Comece por [Protocolo USync](/pt-BR/advanced/usync) e [Funcionalidades customizadas](/pt-BR/advanced/custom-functionality). + +## Código de conduta + +Seja gentil. Assuma boa intenção. Reportes de assédio podem ser enviados aos mantenedores via GitHub. diff --git a/pt-BR/community/sponsor.mdx b/pt-BR/community/sponsor.mdx new file mode 100644 index 0000000..235c3a8 --- /dev/null +++ b/pt-BR/community/sponsor.mdx @@ -0,0 +1,31 @@ +--- +title: "Patrocine o Baileys" +description: "Apoie o desenvolvimento contínuo do Baileys e desbloqueie benefícios para patrocinadores." +--- + +O Baileys é mantido por voluntários. Patrocínios financiam desenvolvimento contínuo, triagem mais rápida de issues e infraestrutura para testar o comportamento novo do WhatsApp. + +## Como patrocinar + +Você pode patrocinar o desenvolvimento contínuo do Baileys através de [purpshell.dev/sponsor](https://purpshell.dev/sponsor). purpshell é o mantenedor ativo atual. Tanto patrocínios únicos quanto recorrentes são suportados. + +## Benefícios + +Patrocinadores ativos recebem: + +- Prioridade em bug reports e feature requests que enviarem. +- Acesso antecipado a guias de migração e avisos de mudanças incompatíveis. +- Reconhecimento nas notas de release e no README do projeto, quando desejado. + + + Tiers específicos de patrocínio e benefícios evoluem ao longo do tempo. Consulte a página do GitHub Sponsors para a lista atual. + + +## Outras formas de apoiar + +Se patrocinar não for uma opção, ainda há formas de ajudar: + +- Envie bug reports detalhados com reproduções. +- Melhore a [documentação](/pt-BR/community/contributing). +- Ajude a triar issues no GitHub e responder perguntas da comunidade. +- Traduza a documentação (veja [Traduções](/pt-BR/community/translations)). diff --git a/pt-BR/community/translations.mdx b/pt-BR/community/translations.mdx new file mode 100644 index 0000000..9bc4008 --- /dev/null +++ b/pt-BR/community/translations.mdx @@ -0,0 +1,24 @@ +--- +title: "Traduções" +description: "Ajude a traduzir a documentação do Baileys para mais línguas." +--- + +Esta documentação é escrita em inglês. Queremos torná-la acessível ao maior número possível de pessoas desenvolvedoras crowdsourcing traduções. + +## Idiomas que estamos priorizando + +1. Português (Brasil) +2. Espanhol +3. Indonésio / Malaio +4. Russo +5. Chinês (Simplificado) + +## Como contribuir + +- Abra uma issue em [WhiskeySockets/docs](https://github.com/WhiskeySockets/docs) declarando a língua. +- Espere um mantenedor confirmar o escopo. +- Envie traduções como pull requests, página por página. Mantenha exemplos de código no original. + + + Ainda estamos finalizando a infraestrutura de traduções. Abra uma issue e um mantenedor te guia. + diff --git a/pt-BR/concepts/data-store.mdx b/pt-BR/concepts/data-store.mdx new file mode 100644 index 0000000..1eed4bc --- /dev/null +++ b/pt-BR/concepts/data-store.mdx @@ -0,0 +1,240 @@ +--- +title: "Implementar um data store para o Baileys" +description: "O Baileys não tem persistência embutida. Aprenda a usar um store em memória ou construir um custom data store." +--- + +O Baileys é um cliente WebSocket sem estado. Ele processa e emite eventos, mas não armazena nada entre reinicializações. Sua aplicação é responsável por construir e manter esse estado a partir dos eventos que o Baileys emite. + +--- + +## Por que você precisa de um store + +- **Reentrega de mensagens** — quando uma mensagem falha ao decifrar, o WhatsApp pede que o remetente reenvie. O Baileys chama `getMessage` para recuperar o texto plano. +- **Decifrar votos de enquete** — votos chegam como eventos `messages.update` cifrados. Para agregar, você precisa da mensagem original da enquete. +- **Consultas de histórico** — `sock.fetchMessageHistory` carrega mensagens antigas, entregues via `messaging-history.set`. + +--- + +## O store em memória + + + `makeInMemoryStore` foi removido no v7. Se você está atualizando do v6, precisa implementar seu próprio store. + + +```typescript +import makeWASocket, { + WAMessage, + WAMessageKey, + Chat, + Contact, + proto, + useMultiFileAuthState, +} from '@whiskeysockets/baileys' + +const messages = new Map() +const chats = new Map() +const contacts = new Map() + +function messageKey(key: WAMessageKey): string { + return `${key.remoteJid}:${key.id}` +} + +const { state, saveCreds } = await useMultiFileAuthState('baileys_auth_info') + +const sock = makeWASocket({ + auth: state, + getMessage: async (key) => { + return messages.get(messageKey(key))?.message ?? undefined + }, +}) + +sock.ev.process(async (events) => { + if (events['creds.update']) { + await saveCreds() + } + + if (events['messages.upsert']) { + for (const msg of events['messages.upsert'].messages) { + if (msg.key.id) { + messages.set(messageKey(msg.key), msg) + } + } + } + + if (events['messages.update']) { + for (const { key, update } of events['messages.update']) { + const existing = messages.get(messageKey(key)) + if (existing) { + messages.set(messageKey(key), { ...existing, ...update }) + } + } + } + + if (events['messaging-history.set']) { + for (const msg of events['messaging-history.set'].messages) { + if (msg.key.id) { + messages.set(messageKey(msg.key), msg) + } + } + for (const chat of events['messaging-history.set'].chats) { + chats.set(chat.id, chat) + } + for (const contact of events['messaging-history.set'].contacts) { + contacts.set(contact.id, contact) + } + } + + if (events['chats.upsert']) { + for (const chat of events['chats.upsert']) { + chats.set(chat.id, chat) + } + } + if (events['chats.update']) { + for (const update of events['chats.update']) { + const existing = chats.get(update.id!) + if (existing) { + chats.set(update.id!, { ...existing, ...update }) + } + } + } + if (events['chats.delete']) { + for (const jid of events['chats.delete']) { + chats.delete(jid) + } + } + + if (events['contacts.upsert']) { + for (const contact of events['contacts.upsert']) { + contacts.set(contact.id, contact) + } + } + if (events['contacts.update']) { + for (const update of events['contacts.update']) { + if (update.id) { + const existing = contacts.get(update.id) + contacts.set(update.id, { ...existing, ...update } as Contact) + } + } + } +}) +``` + +--- + +## Produção: store em banco de dados + +### SQLite (com `better-sqlite3`) + +```typescript +import Database from 'better-sqlite3' +import { WAMessageKey, proto } from '@whiskeysockets/baileys' + +const db = new Database('./baileys.db') + +db.exec(` + CREATE TABLE IF NOT EXISTS messages ( + jid TEXT NOT NULL, + id TEXT NOT NULL, + data TEXT NOT NULL, + PRIMARY KEY (jid, id) + ) +`) + +const insertMsg = db.prepare( + 'INSERT OR REPLACE INTO messages (jid, id, data) VALUES (?, ?, ?)' +) +const getMsg = db.prepare( + 'SELECT data FROM messages WHERE jid = ? AND id = ?' +) + +export async function saveMessage(msg: proto.IWebMessageInfo) { + if (msg.key.remoteJid && msg.key.id) { + insertMsg.run(msg.key.remoteJid, msg.key.id, JSON.stringify(msg.message)) + } +} + +export async function getMessage( + key: WAMessageKey +): Promise { + const row = getMsg.get(key.remoteJid, key.id) as { data: string } | undefined + return row ? JSON.parse(row.data) : undefined +} +``` + +```typescript +const sock = makeWASocket({ + auth: state, + getMessage, +}) + +sock.ev.on('messages.upsert', ({ messages }) => { + for (const msg of messages) { + saveMessage(msg) + } +}) +``` + +### Redis + +```typescript +import { createClient } from 'redis' +import { WAMessageKey, proto } from '@whiskeysockets/baileys' + +const redis = createClient() +await redis.connect() + +export async function saveMessage(msg: proto.IWebMessageInfo) { + if (!msg.key.remoteJid || !msg.key.id || !msg.message) return + const key = `msg:${msg.key.remoteJid}:${msg.key.id}` + await redis.set(key, JSON.stringify(msg.message), { EX: 60 * 60 * 24 * 30 }) +} + +export async function getMessage( + key: WAMessageKey +): Promise { + const raw = await redis.get(`msg:${key.remoteJid}:${key.id}`) + return raw ? JSON.parse(raw) : undefined +} +``` + +### Consultando mensagens + +```typescript +const listMessages = db.prepare(` + SELECT data FROM messages + WHERE jid = ? + ORDER BY rowid DESC + LIMIT ? +`) + +export function loadMessages(jid: string, count: number): proto.IMessage[] { + const rows = listMessages.all(jid, count) as { data: string }[] + return rows.map(r => JSON.parse(r.data)).reverse() +} +``` + +--- + +## Checklist + + + + Sem isso, reentregas e decifragem de votos não funcionam. + + + Guarde o objeto `WAMessage` completo, não só o texto. + + + Use insert em lote para não martelar o banco. + + + Escute `chats.*` e `contacts.*`. + + + Auth state e mensagens são coisas diferentes. Ambos precisam sobreviver a reinicializações. + + + + + Nunca guarde estado de autenticação na mesma tabela das mensagens. Auth state contém chaves Signal de longa duração — trate como uma chave SSH privada. + diff --git a/pt-BR/concepts/events.mdx b/pt-BR/concepts/events.mdx new file mode 100644 index 0000000..8b6a376 --- /dev/null +++ b/pt-BR/concepts/events.mdx @@ -0,0 +1,360 @@ +--- +title: "Lidar com eventos em tempo real no Baileys" +description: "O Baileys emite eventos tipados para mensagens, estado de conexão, grupos, contatos e mais." +--- + +O Baileys comunica tudo que acontece na sua conexão WhatsApp por um event emitter tipado disponível em `sock.ev`. Cada nome de evento mapeia para um payload específico definido em `BaileysEventMap`. + +## Escutando eventos + +Use `sock.ev.on` para se inscrever e `sock.ev.off` para sair. + +```typescript +sock.ev.on('messages.upsert', ({ messages, type }) => { + console.log(`Received ${messages.length} message(s) of type "${type}"`) +}) + +const handler = ({ messages }) => { /* ... */ } +sock.ev.on('messages.upsert', handler) +sock.ev.off('messages.upsert', handler) +``` + +A interface `BaileysEventEmitter`: + +```typescript +export interface BaileysEventEmitter { + on(event: T, listener: (arg: BaileysEventMap[T]) => void): void + off(event: T, listener: (arg: BaileysEventMap[T]) => void): void + removeAllListeners(event: T): void + emit(event: T, arg: BaileysEventMap[T]): boolean +} +``` + +## O padrão `ev.process()` + +Para a maioria das aplicações, use `sock.ev.process` em vez de chamadas individuais a `sock.ev.on`. O callback recebe um mapa de todos os eventos que dispararam num único tick. + +```typescript +sock.ev.process(async (events) => { + if (events['connection.update']) { + const { connection, lastDisconnect } = events['connection.update'] + if (connection === 'close') { + const shouldReconnect = + (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut + if (shouldReconnect) startSock() + } + } + + if (events['creds.update']) { + await saveCreds() + } + + if (events['messages.upsert']) { + const { messages, type } = events['messages.upsert'] + if (type === 'notify') { + for (const msg of messages) { + // trata mensagem recebida + } + } + } +}) +``` + + + `ev.process` agrupa eventos disparados no mesmo tick. Se uma sincronização entrega 500 mensagens e 200 atualizações juntas, seu handler recebe tudo numa chamada só. + + +## Referência de eventos + +### `connection.update` + +Dispara sempre que o estado do WebSocket muda. Payload é `Partial`: + +```typescript +type ConnectionState = { + connection: 'open' | 'connecting' | 'close' + lastDisconnect?: { error: Boom | Error | undefined; date: Date } + isNewLogin?: boolean + qr?: string + receivedPendingNotifications?: boolean + isOnline?: boolean +} +``` + +```typescript +sock.ev.on('connection.update', (update) => { + const { connection, lastDisconnect, qr } = update + + if (qr) { + // renderize o QR aqui + } + + if (connection === 'close') { + const code = (lastDisconnect?.error as Boom)?.output?.statusCode + if (code !== DisconnectReason.loggedOut) { + startSock() + } + } +}) +``` + +### `creds.update` + +Dispara sempre que suas credenciais mudam. Você deve persistir imediatamente. + +```typescript +sock.ev.on('creds.update', saveCreds) +``` + +### `messages.upsert` + +Evento principal para mensagens recebidas e enviadas. + +```typescript +{ + messages: WAMessage[] + type: 'notify' | 'append' + requestId?: string +} +``` + +- `type: 'notify'` — mensagens em tempo real. Devem disparar notificações. +- `type: 'append'` — mensagens de histórico/backfill. Não notifique. + +```typescript +sock.ev.on('messages.upsert', ({ messages, type }) => { + if (type !== 'notify') return + + for (const msg of messages) { + const text = + msg.message?.conversation ?? + msg.message?.extendedTextMessage?.text + if (text) { + console.log(`[${msg.key.remoteJid}] ${text}`) + } + } +}) +``` + + + Sempre itere com `for...of`. O array pode conter mais de uma mensagem por evento. + + +### `messages.update` + +Status de mensagens — recibos, reações, votos de enquete. + +```typescript +sock.ev.on('messages.update', async (updates) => { + for (const { key, update } of updates) { + if (update.status) { + // 1 = enviado, 2 = recebido, 3 = lido, 4 = reproduzido + console.log(`Message ${key.id} status: ${update.status}`) + } + + if (update.pollUpdates) { + const pollCreation = await getMessage(key) + if (pollCreation) { + const result = getAggregateVotesInPollMessage({ + message: pollCreation, + pollUpdates: update.pollUpdates, + }) + console.log('Poll vote aggregation:', result) + } + } + } +}) +``` + +### `messaging-history.set` + +Lote de sincronização de histórico do celular. + +```typescript +sock.ev.on('messaging-history.set', ({ chats, contacts, messages, isLatest, progress, syncType }) => { + console.log( + `History sync: ${chats.length} chats, ${contacts.length} contacts, ` + + `${messages.length} messages (progress: ${progress}%, latest: ${isLatest})` + ) +}) +``` + + + O histórico é entregue em pedaços em ordem cronológica reversa. `isLatest` no último pedaço indica que terminou. + + +### `chats.upsert` / `chats.update` / `chats.delete` + +```typescript +sock.ev.on('chats.upsert', (chats) => { /* novos chats */ }) +sock.ev.on('chats.update', (updates) => { /* updates parciais */ }) +sock.ev.on('chats.delete', (jids) => { /* chats deletados */ }) +``` + +### `contacts.upsert` / `contacts.update` + +```typescript +sock.ev.on('contacts.upsert', (contacts) => { + for (const contact of contacts) { + console.log(`Contact: ${contact.id} — ${contact.name ?? contact.notify}`) + } +}) + +sock.ev.on('contacts.update', async (updates) => { + for (const contact of updates) { + if (typeof contact.imgUrl !== 'undefined') { + const url = contact.imgUrl + ? await sock.profilePictureUrl(contact.id!).catch(() => null) + : null + console.log(`${contact.id} has a new profile picture: ${url}`) + } + } +}) +``` + +### `groups.upsert` / `groups.update` / `group-participants.update` + +```typescript +sock.ev.on('groups.upsert', (groups) => { /* você foi adicionado */ }) +sock.ev.on('groups.update', (updates) => { /* assunto/desc/settings */ }) + +sock.ev.on('group-participants.update', ({ id, author, participants, action }) => { + // action: 'add' | 'remove' | 'promote' | 'demote' + console.log(`Group ${id}: ${action} by ${author}`) +}) +``` + +### `presence.update` + +```typescript +sock.ev.on('presence.update', ({ id, presences }) => { + for (const [jid, data] of Object.entries(presences)) { + console.log(`${jid} in ${id}: ${data.lastKnownPresence}`) + } +}) + +await sock.presenceSubscribe(jid) +``` + +### `call` + +```typescript +sock.ev.on('call', async (calls) => { + for (const call of calls) { + if (call.status === 'offer') { + await sock.rejectCall(call.id, call.from) + } + } +}) +``` + +## Sequência de eventos na primeira conexão + + + + O socket inicia o handshake. `connection` é `'connecting'`. + + + Se faltam credenciais, `qr` é populado. Após confirmação, `connection` vira `'open'`. + + + Se `syncFullHistory` for `true`, o celular envia mensagens passadas em pedaços. + + + Após o histórico, `receivedPendingNotifications` vira `true`. + + + Daqui pra frente, eventos disparam em tempo real. + + + +## Exemplo completo com `ev.process` + +```typescript +import makeWASocket, { + DisconnectReason, + fetchLatestBaileysVersion, + getAggregateVotesInPollMessage, + makeCacheableSignalKeyStore, + useMultiFileAuthState, +} from '@whiskeysockets/baileys' +import { Boom } from '@hapi/boom' +import P from 'pino' + +const logger = P({ level: 'silent' }) + +async function startSock() { + const { state, saveCreds } = await useMultiFileAuthState('baileys_auth_info') + const { version } = await fetchLatestBaileysVersion() + + const sock = makeWASocket({ + version, + logger, + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, logger), + }, + }) + + sock.ev.process(async (events) => { + if (events['connection.update']) { + const { connection, lastDisconnect, qr } = events['connection.update'] + if (connection === 'close') { + const shouldReconnect = + (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut + if (shouldReconnect) startSock() + } + } + + if (events['creds.update']) { + await saveCreds() + } + + if (events['messaging-history.set']) { + const { chats, contacts, messages, isLatest } = events['messaging-history.set'] + console.log(`History: ${chats.length} chats, ${messages.length} messages, latest=${isLatest}`) + } + + if (events['messages.upsert']) { + const { messages, type } = events['messages.upsert'] + if (type === 'notify') { + for (const msg of messages) { + console.log('New message from', msg.key.remoteJid) + } + } + } + + if (events['messages.update']) { + for (const { key, update } of events['messages.update']) { + if (update.pollUpdates) { + const pollCreation = await getMessage(key) + if (pollCreation) { + console.log( + 'Poll results:', + getAggregateVotesInPollMessage({ + message: pollCreation, + pollUpdates: update.pollUpdates, + }) + ) + } + } + } + } + + if (events['group-participants.update']) { + const { id, participants, action } = events['group-participants.update'] + console.log(`Group ${id}: ${action} — ${participants.map(p => p.id).join(', ')}`) + } + + if (events['call']) { + for (const call of events['call']) { + if (call.status === 'offer') { + await sock.rejectCall(call.id, call.from) + } + } + } + }) + + return sock +} +``` diff --git a/pt-BR/concepts/jids.mdx b/pt-BR/concepts/jids.mdx new file mode 100644 index 0000000..a83d752 --- /dev/null +++ b/pt-BR/concepts/jids.mdx @@ -0,0 +1,308 @@ +--- +title: "JIDs do WhatsApp explicados" +description: "JIDs (Jabber IDs) são como o WhatsApp identifica usuários, grupos e listas de transmissão no Baileys. Aprenda os formatos, a dualidade PN/LID e os helpers." +--- + +O WhatsApp identifica todo participante — usuários, grupos, listas de transmissão e feeds de status — com um **JID** (Jabber ID). JIDs vêm do protocolo XMPP e seguem o formato `local@server`. Você os encontra o tempo todo no Baileys: como destino de `sock.sendMessage`, no `key.remoteJid` de cada mensagem recebida e como parâmetro de consultas a grupos e contatos. + +O WhatsApp moderno identifica a mesma pessoa de duas formas, dependendo do contexto. Ambas são JIDs: + +- **PNJID — Phone Number Jabber Identifier.** Vive em `@s.whatsapp.net` e é derivado do número de telefone do usuário. É o identificador legado e o que você usa para procurar alguém pelo número. +- **LIDJID — Linked Identity Jabber Identifier.** Vive em `@lid` e é um identificador opaco por usuário que o WhatsApp atribui para anonimizar números em grupos, comunidades e outras superfícies compartilhadas. É o identificador padrão no Baileys 7.x e posteriores. + +As duas formas se referem à mesma conta. O WhatsApp permite resolver um PNJID para o seu LIDJID (mas não o contrário) — veja [Resolução PNJID ↔ LIDJID](#resolução-pnjid--lidjid). + +## Formatos de JID + + + + `[ddi][numero]@s.whatsapp.net` + + Exemplo: `5511999999999@s.whatsapp.net` + + + `[lid]@lid` + + Exemplo: `123456789012345@lid` + + + `[timestamp]-[random]@g.us` + + Exemplo: `123456789-123345@g.us` + + + `[timestamp]@broadcast` + + Exemplo: `1234567890@broadcast` + + + `status@broadcast` + + Constante fixa — todas as atualizações de status vão para este JID. + + + `[id]@newsletter` + + Exemplo: `12345@newsletter` + + + +Domínios menos comuns que você pode encontrar: + +| Servidor | Significado | +| --- | --- | +| `@hosted` | PN hospedado — usuário PN roteado pelo Meta hosting | +| `@hosted.lid` | LID hospedado — usuário LID roteado pelo Meta hosting | +| `@bot` | Conta de bot Meta AI / first-party | +| `@c.us` | Domínio legado do WhatsApp (ainda usado para `0@c.us`, JID oficial business e PSAs) | +| `@call` | Sinalização de chamadas de voz/vídeo | + +### Regras do número de telefone + +Ao construir um PNJID a partir de um número: + +- Inclua o código do país (ex.: `1` para EUA, `55` para Brasil). +- Não inclua `+`, `-`, espaços ou parênteses. +- `5511999999999@s.whatsapp.net` está correto; `+55 (11) 99999-9999@s.whatsapp.net` não está. + +```typescript +// Correto: ddi + dígitos, sem símbolos +const jid = '5511999999999@s.whatsapp.net' + +// Verifique se o número existe no WhatsApp antes de mandar mensagem +const [result] = await sock.onWhatsApp(jid) +if (result?.exists) { + await sock.sendMessage(result.jid, { text: 'Olá!' }) +} +``` + + + Sempre use o JID retornado por `sock.onWhatsApp` em vez do que você construiu. O WhatsApp pode normalizar o número para um JID canônico. + + +--- + +## PN ↔ LID: o modelo de identidade dupla + +Desde 2024, o WhatsApp vem migrando de identificadores de número de telefone para LIDs (Linked Identity JIDs). Um LID é opaco e por usuário — esconde o número de telefone subjacente para que sua conta apareça em grupos grandes, comunidades e canais sem vazar o número. + +No Baileys 7.x e posteriores: + +- Novas sessões Signal são criadas em formato LID por padrão. +- Um único usuário tem tanto um PNJID (`...@s.whatsapp.net`) quanto um LIDJID (`...@lid`). Eles se referem à mesma pessoa. +- Os campos `participant` em grupos costumam ser LIDs; `participantAlt` carrega o PN correspondente, e vice-versa. +- `MessageKey.remoteJidAlt` e `MessageKey.participantAlt` dão a você o identificador alternativo para mensagens diretas e mensagens de grupo/transmissão/canal respectivamente. +- O tipo `Contact` agora expõe um único `id` mais campos `phoneNumber` (quando `id` é LID) e `lid` (quando `id` é PN). + + + Não tente "restaurar" PN JIDs no seu app. Migre seu armazenamento, indexação e roteamento para LIDs — o WhatsApp trata LIDs como o identificador canônico daqui pra frente. + + +### Resolução PNJID ↔ LIDJID + +O WhatsApp permite resolver um número para o seu LID. O caminho inverso — de LID para número — não é geralmente suportado. Use `onWhatsApp` para verificações pontuais de existência por PN, e o store `lidMapping` em `sock.signalRepository` para conversões diretas: + +```typescript +// PN → LID (único) +const lid = await sock.signalRepository.lidMapping.getLIDForPN( + '5511999999999@s.whatsapp.net' +) + +// PN → LID (em lote — preferido para volumes) +const mappings = await sock.signalRepository.lidMapping.getLIDsForPNs([ + '5511999999999@s.whatsapp.net', + '447911123456@s.whatsapp.net', +]) +// [{ pn: '5511999999999@s.whatsapp.net', lid: '...@lid' }, ...] + +// LID → PN (apenas se o Baileys já tiver visto o mapeamento antes) +const pn = await sock.signalRepository.lidMapping.getPNForLID('123456789012345@lid') +``` + +Um evento `lid-mapping.update` dispara sempre que o Baileys aprende um novo par PN ↔ LID pela rede. Para consultas de diretório mais avançadas, veja [Protocolo USync](/pt-BR/advanced/usync). + +### Compartilhamento de número entre contas + +Como LIDs escondem números por padrão, o WhatsApp fornece flags explícitas de opt-in para trocá-los quando ambos os lados concordam: + +- Empresas podem solicitar o número do destinatário com `{ requestPhoneNumber: true }` em uma mensagem enviada. +- Usuários podem compartilhar seu número com `{ sharePhoneNumber: true }`. + +Empresas usam LIDs desde 2023; usuários foram incluídos ao longo de 2024. + +--- + +## JIDs multi-aparelho + +No protocolo multi-aparelho do WhatsApp, uma única conta pode ter múltiplos aparelhos conectados. Cada aparelho recebe um sufixo: `5511999999999:2@s.whatsapp.net` ou `123456789012345:3@lid`. A parte antes do `:` é o usuário, e o número depois é o ID do aparelho. + +É por isso que você nunca deve comparar ou dividir JIDs com operações de string — uma mensagem do aparelho `:0` e uma do `:2` pertencem ao mesmo usuário. + +--- + +## Helpers de JID + +O Baileys exporta um conjunto de helpers em `@whiskeysockets/baileys`. Sempre use eles em vez de manipulação manual de string. + +### Parsing e codificação + +```typescript +import { jidDecode, jidEncode, jidNormalizedUser } from '@whiskeysockets/baileys' + +// Decodifica um JID em seus componentes +const decoded = jidDecode('5511999999999:2@s.whatsapp.net') +// { user: '5511999999999', server: 's.whatsapp.net', device: 2, domainType: 0 } + +// Decodifica um LIDJID +const lidDecoded = jidDecode('123456789012345:3@lid') +// { user: '123456789012345', server: 'lid', device: 3, domainType: 1 } + +// Normaliza um JID — remove sufixo de aparelho/agente +const normalized = jidNormalizedUser('5511999999999:2@s.whatsapp.net') +// '5511999999999@s.whatsapp.net' + +// Codifica componentes de volta em uma string JID +const encoded = jidEncode('5511999999999', 's.whatsapp.net') +// '5511999999999@s.whatsapp.net' +``` + +O campo `domainType` no resultado decodificado corresponde ao enum `WAJIDDomains`: + +| Valor | Constante | Domínio | +| --- | --- | --- | +| `0` | `WHATSAPP` | `s.whatsapp.net` | +| `1` | `LID` | `lid` | +| `128` | `HOSTED` | `hosted` | +| `129` | `HOSTED_LID` | `hosted.lid` | + +### Verificações de tipo + +```typescript +import { + isJidGroup, + isJidBroadcast, + isJidStatusBroadcast, + isJidNewsletter, + isPnUser, + isLidUser, + isHostedPnUser, + isHostedLidUser, + isJidMetaAI, + areJidsSameUser, +} from '@whiskeysockets/baileys' + +isJidGroup('123456789-123345@g.us') // true +isJidBroadcast('1234567890@broadcast') // true +isJidStatusBroadcast('status@broadcast') // true +isJidNewsletter('12345@newsletter') // true +isPnUser('5511999999999@s.whatsapp.net') // true (PNJID) +isLidUser('123456789012345@lid') // true (LIDJID) +isHostedPnUser('5511999999999@hosted') // true +isHostedLidUser('123456789012345@hosted.lid') // true +isJidMetaAI('13135550002@bot') // true + +areJidsSameUser( + '5511999999999:0@s.whatsapp.net', + '5511999999999:3@s.whatsapp.net' +) // true +``` + + + `areJidsSameUser` compara somente a parte do usuário — não cruza fronteiras PN/LID. Para verificar se um PNJID e um LIDJID se referem à mesma conta, resolva ambos para a mesma forma antes via `getLIDForPN`. + + + + `isJidUser` de versões anteriores foi removido. Use `isPnUser` ou `isLidUser` dependendo da forma desejada. + + +### Constantes comuns + +```typescript +import { + STORIES_JID, + S_WHATSAPP_NET, + OFFICIAL_BIZ_JID, + META_AI_JID, +} from '@whiskeysockets/baileys' + +STORIES_JID // 'status@broadcast' +S_WHATSAPP_NET // '@s.whatsapp.net' +OFFICIAL_BIZ_JID // '16505361212@c.us' +META_AI_JID // '13135550002@c.us' +``` + +--- + +## Padrões práticos + +### Roteamento de mensagens por tipo + +```typescript +import { isJidGroup, isJidBroadcast } from '@whiskeysockets/baileys' + +sock.ev.on('messages.upsert', ({ messages, type }) => { + if (type !== 'notify') return + for (const msg of messages) { + const jid = msg.key.remoteJid! + if (isJidGroup(jid)) { + handleGroupMessage(msg) + } else if (isJidBroadcast(jid)) { + // ignora mensagens de transmissão + } else { + handleDirectMessage(msg) + } + } +}) +``` + +### Filtrando eventos com `shouldIgnoreJid` + +```typescript +import { isJidBroadcast, isJidNewsletter } from '@whiskeysockets/baileys' + +const sock = makeWASocket({ + auth: state, + shouldIgnoreJid: (jid) => isJidBroadcast(jid) || isJidNewsletter(jid), +}) +``` + +### Verificar se dois JIDs são do mesmo usuário + +```typescript +import { areJidsSameUser } from '@whiskeysockets/baileys' + +const isSameUser = areJidsSameUser( + msg.key.participant, + sock.user?.id +) +``` + +### Deduplicar PN e LID do mesmo usuário + +```typescript +import { isLidUser } from '@whiskeysockets/baileys' + +async function canonicalLid(jid: string): Promise { + if (isLidUser(jid)) return jid + const lid = await sock.signalRepository.lidMapping.getLIDForPN(jid) + return lid ?? jid +} +``` + +--- + +## O que não fazer + + + Nunca divida uma string JID com `.split('@')` ou compare JIDs com `===`. JIDs podem ter sufixos de aparelho (`:2`), campos de agente, domínios alternativos (`@lid`, `@hosted.lid`) ou dualidade PN/LID que fazem comparações de string falharem silenciosamente. + + ```typescript + // Errado — perde variantes de aparelho e ignora dualidade PN/LID + const user = jid.split('@')[0] + const isSame = jid1 === jid2 + + // Certo — use os helpers fornecidos + const { user } = jidDecode(jid)! + const isSame = areJidsSameUser(jid1, jid2) + ``` + diff --git a/pt-BR/concepts/socket-config.mdx b/pt-BR/concepts/socket-config.mdx new file mode 100644 index 0000000..6cc6a0d --- /dev/null +++ b/pt-BR/concepts/socket-config.mdx @@ -0,0 +1,301 @@ +--- +title: "Configurar a conexão do Baileys" +description: "Explore todas as opções de SocketConfig do Baileys: auth, browser, logger, cache, retries, timeouts e mais." +--- + +Toda conexão Baileys começa chamando `makeWASocket` com um objeto `SocketConfig`. A maioria das opções tem valores padrão sensatos definidos em `DEFAULT_CONNECTION_CONFIG`. + +## Opções obrigatórias + +### `auth` + +O único campo verdadeiramente obrigatório. Você deve passar um objeto `AuthenticationState`. + +```typescript +import makeWASocket, { useMultiFileAuthState, makeCacheableSignalKeyStore } from '@whiskeysockets/baileys' +import P from 'pino' + +const { state, saveCreds } = await useMultiFileAuthState('baileys_auth_info') + +const sock = makeWASocket({ + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, logger), + }, +}) + +sock.ev.on('creds.update', saveCreds) +``` + + + Quando uma mensagem é enviada ou recebida, sessões do Signal são atualizadas e `authState.keys.set()` é chamado. Se você não persistir essas atualizações imediatamente, mensagens vão falhar ao decifrar. + + +--- + +## Identidade e navegador + +### `browser` + +Controla como o Baileys se identifica para o WhatsApp. Use a constante `Browsers` em vez de escrever a tupla manualmente — é também o nome que aparece em **Aparelhos conectados**. + +```typescript +import makeWASocket, { Browsers } from '@whiskeysockets/baileys' + +const sock = makeWASocket({ + auth: state, + browser: Browsers.macOS('Chrome'), + + // Outras opções: + // browser: Browsers.ubuntu('My App') + // browser: Browsers.windows('Firefox') + // browser: Browsers.appropriate('Safari') +}) +``` + +| Preset | Exemplo | +| --- | --- | +| `Browsers.ubuntu('My App')` | Ubuntu — My App | +| `Browsers.macOS('Desktop')` | macOS — Desktop | +| `Browsers.windows('My App')` | Windows — My App | + +O navegador escolhido também afeta quanto histórico o WhatsApp envia na primeira sincronização. Identidades desktop recebem significativamente mais histórico que mobile. + +### Receber histórico completo de mensagens + +Por padrão, o Baileys conecta com um perfil Chrome, o que limita quanto histórico o WhatsApp entrega. Para histórico completo, defina `syncFullHistory: true` e use `Browsers.macOS('Desktop')`. + +```typescript +import makeWASocket, { Browsers } from '@whiskeysockets/baileys' + +const sock = makeWASocket({ + auth: state, + browser: Browsers.macOS('Desktop'), + syncFullHistory: true, +}) +``` + +As mensagens de histórico chegam de forma assíncrona pelo evento `messaging-history.set`. Veja [Sincronizar histórico de conversa](/pt-BR/advanced/history-sync). + + + Pedir histórico completo aumenta significativamente o tempo de inicialização e o uso de memória. + + +### `logger` + +Aceita qualquer logger compatível com pino. Passe `level: 'debug'` para ver cada frame binário. + +```typescript +import P from 'pino' + +const logger = P({ level: 'silent' }) +const devLogger = P({ level: 'debug' }) +``` + +--- + +## Comportamento da conexão + +### `markOnlineOnConnect` + +**Padrão:** `true`. Quando `true`, o Baileys se marca como online ao conectar. Defina `false` se quiser que o celular continue recebendo notificações. + +### `syncFullHistory` + +**Padrão:** `true`. Pede ao celular para entregar o histórico completo. Se você só precisa de mensagens recentes, defina `false`. + +### `printQRInTerminal` + + + Esta opção está obsoleta. Use o evento `connection.update` para ler o campo `qr` e renderize você mesmo. + + +--- + +## Confiabilidade de mensagens + +### `getMessage` + +Callback que recebe um `WAMessageKey` e retorna a `proto.IMessage`. Necessário para reentregas e decifrar votos de enquete. + +```typescript +const messageStore = new Map() + +const sock = makeWASocket({ + auth: state, + getMessage: async (key) => { + const id = `${key.remoteJid}:${key.id}` + return messageStore.get(id) + }, +}) + +sock.ev.on('messages.upsert', ({ messages }) => { + for (const msg of messages) { + if (msg.key.id && msg.message) { + const id = `${msg.key.remoteJid}:${msg.key.id}` + messageStore.set(id, msg.message) + } + } +}) +``` + +### `msgRetryCounterCache` + +Cache que conta quantas vezes o Baileys tentou reenviar. + +```typescript +import NodeCache from '@cacheable/node-cache' +import { CacheStore } from '@whiskeysockets/baileys' + +const msgRetryCounterCache = new NodeCache() as CacheStore + +const sock = makeWASocket({ + auth: state, + msgRetryCounterCache, +}) +``` + +### `maxMsgRetryCount` + +**Padrão:** `5`. + +--- + +## Performance em grupos + +### `cachedGroupMetadata` + +```typescript +import NodeCache from '@cacheable/node-cache' + +const groupCache = new NodeCache({ stdTTL: 5 * 60, useClones: false }) + +const sock = makeWASocket({ + auth: state, + cachedGroupMetadata: async (jid) => groupCache.get(jid), +}) + +sock.ev.on('groups.update', async ([event]) => { + const metadata = await sock.groupMetadata(event.id) + groupCache.set(event.id, metadata) +}) + +sock.ev.on('group-participants.update', async (event) => { + const metadata = await sock.groupMetadata(event.id) + groupCache.set(event.id, metadata) +}) +``` + +--- + +## Filtragem de eventos + +### `shouldIgnoreJid` + +```typescript +import { isJidBroadcast, isJidNewsletter } from '@whiskeysockets/baileys' + +const sock = makeWASocket({ + auth: state, + shouldIgnoreJid: (jid) => isJidBroadcast(jid) || isJidNewsletter(jid), +}) +``` + +--- + +## Timeouts e keep-alive + +| Opção | Padrão | Descrição | +| --- | --- | --- | +| `connectTimeoutMs` | `20_000` | Falha a conexão se o WebSocket não abrir dentro deste período. | +| `defaultQueryTimeoutMs` | `60_000` | Tempo máximo para esperar uma resposta de query IQ. | +| `keepAliveIntervalMs` | `30_000` | Intervalo entre frames ping do WebSocket. | +| `retryRequestDelayMs` | `250` | Delay entre requisições sucessivas de reentrega. | + +--- + +## Pré-visualização de links + +### `generateHighQualityLinkPreview` + +**Padrão:** `false`. Quando `true`, o Baileys faz upload da miniatura para os servidores de mídia. Requer `link-preview-js`. + +```typescript +const sock = makeWASocket({ + auth: state, + generateHighQualityLinkPreview: true, +}) +``` + +--- + +## Exemplo pronto para produção + +```typescript +import makeWASocket, { + Browsers, + CacheStore, + makeCacheableSignalKeyStore, + useMultiFileAuthState, + isJidBroadcast, + fetchLatestBaileysVersion, + proto, +} from '@whiskeysockets/baileys' +import NodeCache from '@cacheable/node-cache' +import P from 'pino' + +const logger = P({ level: 'silent' }) + +const msgRetryCounterCache = new NodeCache() as CacheStore +const groupCache = new NodeCache({ stdTTL: 5 * 60, useClones: false }) +const messageStore = new Map() + +async function startSock() { + const { state, saveCreds } = await useMultiFileAuthState('baileys_auth_info') + const { version } = await fetchLatestBaileysVersion() + + const sock = makeWASocket({ + version, + logger, + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, logger), + }, + browser: Browsers.macOS('Chrome'), + markOnlineOnConnect: false, + syncFullHistory: false, + generateHighQualityLinkPreview: true, + msgRetryCounterCache, + maxMsgRetryCount: 5, + connectTimeoutMs: 20_000, + defaultQueryTimeoutMs: 60_000, + keepAliveIntervalMs: 30_000, + shouldIgnoreJid: (jid) => isJidBroadcast(jid), + getMessage: async (key) => { + const id = `${key.remoteJid}:${key.id}` + return messageStore.get(id) + }, + cachedGroupMetadata: async (jid) => groupCache.get(jid), + }) + + sock.ev.on('creds.update', saveCreds) + + sock.ev.on('messages.upsert', ({ messages }) => { + for (const msg of messages) { + if (msg.key.id && msg.message) { + messageStore.set(`${msg.key.remoteJid}:${msg.key.id}`, msg.message) + } + } + }) + + sock.ev.on('groups.update', async ([event]) => { + groupCache.set(event.id, await sock.groupMetadata(event.id)) + }) + + sock.ev.on('group-participants.update', async (event) => { + groupCache.set(event.id, await sock.groupMetadata(event.id)) + }) + + return sock +} +``` diff --git a/pt-BR/faq.mdx b/pt-BR/faq.mdx new file mode 100644 index 0000000..daad391 --- /dev/null +++ b/pt-BR/faq.mdx @@ -0,0 +1,83 @@ +--- +title: "Perguntas frequentes" +description: "Perguntas frequentes sobre o uso do Baileys, de peculiaridades de conexão e entrega de mensagens a LIDs e sincronização de histórico." +--- + +Esta página reúne respostas para as perguntas que aparecem com mais frequência em issues e na comunidade. Se a sua não está aqui, navegue pela documentação ou abra uma discussão no [GitHub](https://github.com/WhiskeySockets/Baileys/discussions). + +## O Baileys é afiliado ao WhatsApp? + +Não. O Baileys é uma biblioteca open source independente que se conecta ao protocolo WebSocket do WhatsApp Web pela funcionalidade de [Aparelhos Conectados](https://faq.whatsapp.com/378279804439436). Ele **não** usa a [WhatsApp Business API](https://developers.facebook.com/docs/whatsapp/overview/business-accounts/). Use por sua conta e risco — não faça spam e não construa stalkerware. + +## Por que meu socket desconecta logo depois que escaneio o QR? + +Isso é esperado. Após escanear o QR, o WhatsApp força uma reconexão para que o Baileys apresente as credenciais completas. Observe `DisconnectReason.restartRequired` em `connection.update` e reabra o socket. Veja [Conectar com QR code](/pt-BR/authentication/qr-code). + +## Por que o QR code não está aparecendo no meu terminal? + +A opção `printQRInTerminal` está **obsoleta**. Escute o campo `qr` em `connection.update` e renderize você mesmo com uma biblioteca como [`qrcode-terminal`](https://www.npmjs.com/package/qrcode-terminal) ou [`qrcode`](https://www.npmjs.com/package/qrcode). + +```ts +import qrcode from 'qrcode-terminal' + +sock.ev.on('connection.update', ({ qr }) => { + if (qr) qrcode.generate(qr, { small: true }) +}) +``` + +## Por que minhas mensagens ficam presas em "esta mensagem pode demorar"? + +O sistema de reentrega do WhatsApp exige que você devolva a mensagem original quando a entrega falhar. Implemente [`getMessage`](/pt-BR/concepts/socket-config) na configuração do seu socket para que o Baileys consiga recriptografar e reenviar. + +## Devo usar `useMultiFileAuthState` em produção? + +Não. É adequado para desenvolvimento e bots pequenos, mas faz I/O em disco que não escala. Use a [implementação](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/use-multi-file-auth-state.ts) como referência e respalde sua autenticação real com um banco de dados. Veja [Salvar e restaurar sessões do WhatsApp](/pt-BR/authentication/session-management). + +## Como recebo o histórico completo da conversa? + +Defina `syncFullHistory: true` e use um preset de navegador desktop. Ambas as opções fazem parte da configuração do socket — veja [Receber histórico completo](/pt-BR/concepts/socket-config#receber-histórico-completo-de-mensagens). + +```ts +import makeWASocket, { Browsers } from '@whiskeysockets/baileys' + +const sock = makeWASocket({ + browser: Browsers.macOS('Desktop'), + syncFullHistory: true, +}) +``` + +O WhatsApp entrega o histórico de forma assíncrona pelo evento `messaging-history.set` após a conexão abrir. Históricos grandes aumentam consideravelmente o tempo de inicialização e o consumo de memória. + +## O que é um LID e por que vejo isso em vez de números de telefone? + +Um **LIDJID** (Linked Identity Jabber Identifier, em `@lid`) é um identificador anônimo por usuário que o WhatsApp atribui a cada conta. A forma legada de número de telefone é o **PNJID** (Phone Number Jabber Identifier, em `@s.whatsapp.net`). Por padrão, todas as novas sessões Signal são baseadas em LID a partir do Baileys 7.x. Você consegue resolver um PNJID para o seu LIDJID via `onWhatsApp()` ou `getLIDForPN`, mas o caminho inverso geralmente não funciona. Não tente "restaurar" PN JIDs no seu app — migre para LIDs. Veja [JIDs do WhatsApp explicados](/pt-BR/concepts/jids) e [Migrar para o Baileys v7](/pt-BR/migration/v7). + +## Por que estou sendo limitado ou banido ao enviar para grupos? + +Toda chamada `sendMessage` para um grupo busca a lista de participantes para criptografar para cada membro. Sem cache, isso atinge o rate limit rapidamente. Forneça um callback `cachedGroupMetadata` na configuração do socket: + +```ts +const groupCache = new NodeCache(/* ... */) + +const sock = makeWASocket({ + cachedGroupMetadata: async (jid) => groupCache.get(jid), +}) +``` + +## Devo sempre atualizar para a última versão do WhatsApp Web? + +Não. Evite chamar `fetchLatestWaWebVersion` em toda conexão — versões mais novas podem ser incompatíveis. Fique uma ou duas versões atrás e só sobrescreva a versão quando souber que seus protobufs combinam. + +## Como evito que as notificações sumam do meu celular? + +O Baileys marca sua presença como online ao conectar por padrão, o que suprime as notificações do celular. Defina `markOnlineOnConnect: false` na configuração do socket. Você também pode enviar `sock.sendPresenceUpdate('unavailable')` periodicamente. + +## A Mobile API é suportada? + +Não. O Baileys só suporta o protocolo do WhatsApp Web via Aparelhos Conectados. A autenticação por código de pareamento **não** é a Mobile API — é uma alternativa ao QR code para o mesmo fluxo de Aparelhos Conectados. + +## Onde reporto bugs ou peço ajuda? + +- Bugs: [GitHub Issues](https://github.com/WhiskeySockets/Baileys/issues) +- Discussão: [GitHub Discussions](https://github.com/WhiskeySockets/Baileys/discussions) +- Migração: [Migrar para o Baileys v7](/pt-BR/migration/v7) e [Migrar para o Baileys v8](/pt-BR/migration/v8) diff --git a/pt-BR/features/broadcasts-stories.mdx b/pt-BR/features/broadcasts-stories.mdx new file mode 100644 index 0000000..bdf7056 --- /dev/null +++ b/pt-BR/features/broadcasts-stories.mdx @@ -0,0 +1,74 @@ +--- +title: "Enviar transmissões e Stories do WhatsApp com o Baileys" +description: "Publique status (Stories) e envie mensagens a listas de transmissão." +--- + +| Alvo | Formato JID | +| --- | --- | +| Lista de transmissão | `[timestamp de criação]@broadcast` | +| Status (Stories) | `status@broadcast` | + +## Enviar transmissão ou status + +```typescript +await sock.sendMessage( + jid, + { + image: { + url: url + }, + caption: caption + }, + { + backgroundColor: backgroundColor, + font: font, + statusJidList: statusJidList, + broadcast: true + } +) +``` + +| Opção | Descrição | +| --- | --- | +| `broadcast` | `true` para habilitar modo de transmissão | +| `statusJidList` | Array de JIDs que vão receber este status | +| `backgroundColor` | Cor de fundo para status de texto | +| `font` | Estilo de fonte para status de texto | + + + `statusJidList` é obrigatório ao publicar em `status@broadcast`. + + +## Tipos suportados + +| Tipo | Caso de uso | +| --- | --- | +| `extendedTextMessage` | Status de texto com fundo/fonte | +| `imageMessage` | Status de foto | +| `videoMessage` | Status de vídeo | +| `voiceMessage` | Status de áudio | + +Veja `AnyRegularMessageContent` e `MiscMessageGenerationOptions`. + +## Enviar para lista de transmissão + +```typescript +await sock.sendMessage('1234567890123456789@broadcast', { text: 'Hello everyone!' }) +``` + +## Consultar nome e destinatários + +```typescript +const bList = await sock.getBroadcastListInfo('1234@broadcast') +console.log(`list name: ${bList.name}, recps: ${bList.recipients}`) +``` + +## Limitações + + + O WhatsApp Web não suporta criar novas listas de transmissão. + + +- Listas devem ser criadas no app móvel primeiro. +- Cada JID é derivado do timestamp de criação. +- Deletar via `chatModify` é suportado, criar não. diff --git a/pt-BR/features/groups.mdx b/pt-BR/features/groups.mdx new file mode 100644 index 0000000..dcad5ef --- /dev/null +++ b/pt-BR/features/groups.mdx @@ -0,0 +1,198 @@ +--- +title: "Criar, configurar e gerenciar grupos do WhatsApp no Baileys" +description: "Crie e gerencie grupos: adicione/remova participantes, atualize configurações, invites, aprove pedidos e configure mensagens efêmeras." +--- + + + Você precisa ser admin para realizar a maioria das operações desta página. Tentar como membro comum lança erro. + + +## Criar um grupo + +```typescript +const group = await sock.groupCreate('My Fab Group', ['1234@s.whatsapp.net', '4564@s.whatsapp.net']) +console.log('created group with id: ' + group.gid) +await sock.sendMessage(group.id, { text: 'hello there' }) +``` + +## Adicionar, remover, promover ou rebaixar + +```typescript +await sock.groupParticipantsUpdate( + jid, + ['abcd@s.whatsapp.net', 'efgh@s.whatsapp.net'], + 'add' // 'remove' | 'demote' | 'promote' +) +``` + +## Atualizar nome e descrição + + + +```typescript Mudar nome +await sock.groupUpdateSubject(jid, 'New Subject!') +``` + +```typescript Mudar descrição +await sock.groupUpdateDescription(jid, 'New Description!') +``` + + + +## Mudar configurações do grupo + +```typescript +await sock.groupSettingUpdate(jid, 'announcement') +await sock.groupSettingUpdate(jid, 'not_announcement') +await sock.groupSettingUpdate(jid, 'unlocked') +await sock.groupSettingUpdate(jid, 'locked') +``` + +| Valor | Efeito | +| --- | --- | +| `announcement` | Apenas admins enviam | +| `not_announcement` | Todos os membros enviam | +| `locked` | Apenas admins mudam configurações | +| `unlocked` | Todos os membros mudam configurações | + +## Sair do grupo + +```typescript +await sock.groupLeave(jid) +``` + +## Links de convite + +### Pegar o código + +```typescript +const code = await sock.groupInviteCode(jid) +const link = 'https://chat.whatsapp.com/' + code +``` + +### Revogar o código + +```typescript +const code = await sock.groupRevokeInvite(jid) +``` + +### Entrar pelo código + +```typescript +const response = await sock.groupAcceptInvite(code) +``` + + + O `code` não inclui o prefixo `https://chat.whatsapp.com/`. + + +### Info do grupo a partir do código + +```typescript +const response = await sock.groupGetInviteInfo(code) +``` + +### Entrar via `groupInviteMessage` + +```typescript +const response = await sock.groupAcceptInviteV4(jid, groupInviteMessage) +``` + +## Consultar metadados do grupo + +```typescript +const metadata = await sock.groupMetadata(jid) +console.log(metadata.id + ', title: ' + metadata.subject + ', description: ' + metadata.desc) +``` + +`GroupMetadata` inclui: + +| Campo | Tipo | Descrição | +| --- | --- | --- | +| `id` | `string` | JID do grupo | +| `subject` | `string` | Nome | +| `desc` | `string \| undefined` | Descrição | +| `owner` | `string \| undefined` | JID do criador | +| `participants` | `GroupParticipant[]` | Lista com flags de admin | +| `ephemeralDuration` | `number \| undefined` | Timer ativo de mensagens efêmeras (segundos) | +| `announce` | `boolean \| undefined` | `true` quando só admins enviam | +| `restrict` | `boolean \| undefined` | `true` quando só admins mudam configurações | +| `memberAddMode` | `boolean \| undefined` | `true` quando todos podem adicionar membros | +| `joinApprovalMode` | `boolean \| undefined` | `true` quando entradas precisam de aprovação | + +### Listar todos os grupos participados + +```typescript +const response = await sock.groupFetchAllParticipating() +``` + +## Aprovações de entrada + +### Listar pedidos pendentes + +```typescript +const response = await sock.groupRequestParticipantsList(jid) +``` + +### Aprovar ou rejeitar + +```typescript +const response = await sock.groupRequestParticipantsUpdate( + jid, + ['abcd@s.whatsapp.net', 'efgh@s.whatsapp.net'], + 'approve' // ou 'reject' +) +``` + +## Mensagens efêmeras + +```typescript +await sock.groupToggleEphemeral(jid, 86400) +``` + +| Duração | Segundos | +| --- | --- | +| Off | `0` | +| 24 horas | `86400` | +| 7 dias | `604800` | +| 90 dias | `7776000` | + +## Mudar quem pode adicionar membros + +```typescript +await sock.groupMemberAddMode( + jid, + 'all_member_add' // ou 'admin_add' +) +``` + +| Modo | Efeito | +| --- | --- | +| `all_member_add` | Qualquer membro pode adicionar | +| `admin_add` | Apenas admins podem adicionar | + +## Cache de metadados de grupo + +```typescript +import NodeCache from '@cacheable/node-cache' + +const groupCache = new NodeCache({ stdTTL: 5 * 60, useClones: false }) + +const sock = makeWASocket({ + cachedGroupMetadata: async (jid) => groupCache.get(jid) +}) + +sock.ev.on('groups.update', async ([event]) => { + const metadata = await sock.groupMetadata(event.id) + groupCache.set(event.id, metadata) +}) + +sock.ev.on('group-participants.update', async (event) => { + const metadata = await sock.groupMetadata(event.id) + groupCache.set(event.id, metadata) +}) +``` + + + Cachear metadados reduz latência e queries IQ ao WhatsApp, especialmente em bots com muitos grupos. + diff --git a/pt-BR/features/presence.mdx b/pt-BR/features/presence.mdx new file mode 100644 index 0000000..8a4b3ba --- /dev/null +++ b/pt-BR/features/presence.mdx @@ -0,0 +1,90 @@ +--- +title: "Inscrever-se e transmitir presença no WhatsApp" +description: "Acompanhe quando contatos estão online, digitando ou gravando usando assinaturas de presença do Baileys." +--- + +## Estados de presença + +| Estado | Significado | +| --- | --- | +| `available` | Online e ativo | +| `unavailable` | Offline ou app em segundo plano | +| `composing` | Digitando | +| `recording` | Gravando áudio | +| `paused` | Começou a digitar mas parou | + +## Inscrever-se em atualizações + +```typescript +sock.ev.on('presence.update', console.log) + +await sock.presenceSubscribe(jid) +``` + +## Padrão completo + +```typescript +import makeWASocket from '@whiskeysockets/baileys' + +await sock.presenceSubscribe(jid) + +sock.ev.on('presence.update', ({ id, presences }) => { + for (const [participant, data] of Object.entries(presences)) { + console.log( + `${participant} in ${id}:`, + data.lastKnownPresence, + 'last seen:', data.lastSeen + ) + } +}) +``` + +Formato do payload: + +```typescript +{ + id: string, + presences: { + [participantJid: string]: { + lastKnownPresence: WAPresence, + lastSeen?: number + } + } +} +``` + + + Em grupos, `presences` mapeia JIDs de participantes. Em conversas 1-a-1, contém uma única entrada. + + +## Transmitir sua própria presença + +```typescript +await sock.sendPresenceUpdate('available', jid) +``` + +| Valor | Quando usar | +| --- | --- | +| `available` | Cliente ativo, usuário presente | +| `unavailable` | Cliente ocioso ou usuário saiu | +| `composing` | Usuário está digitando | +| `recording` | Usuário está gravando áudio | +| `paused` | Usuário parou de digitar sem enviar | + + + Atualizações expiram em ~10 segundos. Para sustentar `composing`, chame repetidamente. + + +## Notificações no celular + +```typescript +const sock = makeWASocket({ + markOnlineOnConnect: false +}) +``` + +Ou: + +```typescript +await sock.sendPresenceUpdate('unavailable') +``` diff --git a/pt-BR/features/privacy.mdx b/pt-BR/features/privacy.mdx new file mode 100644 index 0000000..d5255dd --- /dev/null +++ b/pt-BR/features/privacy.mdx @@ -0,0 +1,104 @@ +--- +title: "Configurar privacidade e bloqueios do WhatsApp no Baileys" +description: "Bloqueie usuários, configure visto por último, recibos de leitura, acesso a foto de perfil e modo padrão de mensagens efêmeras." +--- + +## Bloquear / desbloquear + +```typescript +await sock.updateBlockStatus(jid, 'block') +await sock.updateBlockStatus(jid, 'unblock') +``` + +## Buscar todas as configurações + +```typescript +const privacySettings = await sock.fetchPrivacySettings(true) +``` + +## Buscar lista de bloqueados + +```typescript +const response = await sock.fetchBlocklist() +``` + +## Privacidade do visto por último + +```typescript +const value = 'all' // 'contacts' | 'contact_blacklist' | 'none' +await sock.updateLastSeenPrivacy(value) +``` + +| Valor | Quem vê seu visto por último | +| --- | --- | +| `'all'` | Todos | +| `'contacts'` | Apenas seus contatos | +| `'contact_blacklist'` | Todos exceto uma lista negra | +| `'none'` | Ninguém | + +## Privacidade do online + +```typescript +const value = 'all' // 'match_last_seen' +await sock.updateOnlinePrivacy(value) +``` + +| Valor | Quem vê seu status online | +| --- | --- | +| `'all'` | Todos | +| `'match_last_seen'` | Mesma audiência do visto por último | + +## Privacidade da foto de perfil + +```typescript +const value = 'all' // 'contacts' | 'contact_blacklist' | 'none' +await sock.updateProfilePicturePrivacy(value) +``` + +## Privacidade do status + +```typescript +const value = 'all' // 'contacts' | 'contact_blacklist' | 'none' +await sock.updateStatusPrivacy(value) +``` + +## Recibos de leitura + +```typescript +const value = 'all' // 'none' +await sock.updateReadReceiptsPrivacy(value) +``` + +| Valor | Comportamento | +| --- | --- | +| `'all'` | Envia recibos de leitura a todos | +| `'none'` | Desativa recibos de leitura | + + + Ao desativar, você também para de recebê-los. + + +## Adição em grupos + +```typescript +const value = 'all' // 'contacts' | 'contact_blacklist' +await sock.updateGroupsAddPrivacy(value) +``` + +## Modo padrão de mensagens que desaparecem + +```typescript +const ephemeral = 86400 +await sock.updateDefaultDisappearingMode(ephemeral) +``` + +| Duração | Segundos | +| --- | --- | +| Off | `0` | +| 24 horas | `86400` | +| 7 dias | `604800` | +| 90 dias | `7776000` | + + + Esse padrão se aplica só a conversas novas — não muda as existentes. + diff --git a/pt-BR/installation.mdx b/pt-BR/installation.mdx new file mode 100644 index 0000000..ea2d874 --- /dev/null +++ b/pt-BR/installation.mdx @@ -0,0 +1,141 @@ +--- +title: "Instale o Baileys no seu projeto Node.js" +description: "Guia passo a passo para instalar o Baileys com npm, yarn ou a versão de borda do GitHub. Cobre requisitos do Node.js e dependências opcionais." +--- + +O Baileys é publicado no npm sob o escopo `@whiskeysockets/baileys`. Você pode instalá-lo com npm, yarn, pnpm ou qualquer outro gerenciador de pacotes do Node.js. Esta página cobre a versão estável, a build de borda direto do GitHub e os peer dependencies opcionais que liberam funcionalidades extras. + +## Requisitos + +Você precisa de **Node.js 20.0.0 ou superior**. O Baileys impõe isso através de uma checagem `preinstall` — a instalação falha com uma mensagem de erro clara se sua versão do Node.js for muito antiga. + +Verifique sua versão antes de instalar: + +```bash +node --version +``` + +## Instale a versão estável + +A versão estável é o ponto de partida recomendado para projetos novos. + + + +```bash npm +npm install @whiskeysockets/baileys +``` + +```bash yarn +yarn add @whiskeysockets/baileys +``` + + + +## Instale a versão de borda + +A build de borda é construída direto do branch `master` no GitHub. Inclui as últimas correções e funcionalidades, mas não tem garantia de estabilidade. + +```bash +yarn add github:WhiskeySockets/Baileys +``` + + + O repositório do Baileys usa Yarn 4 internamente via Corepack. Você pode usar npm, yarn ou qualquer outro gerenciador no seu próprio projeto — apenas quem contribui para o repositório do Baileys precisa do Yarn 4. + + +## Importe o Baileys no seu projeto + +Após instalar, importe a exportação padrão no seu arquivo TypeScript ou JavaScript: + +```typescript +import makeWASocket from '@whiskeysockets/baileys' +``` + +Você também pode importar exports nomeados junto com o padrão: + +```typescript +import makeWASocket, { useMultiFileAuthState, DisconnectReason } from '@whiskeysockets/baileys' +``` + +## Peer dependencies opcionais + +O Baileys tem várias peer dependencies opcionais que habilitam funcionalidades adicionais. Instale apenas as que você precisar. + +### Miniaturas de imagens e figurinhas + +O Baileys pode gerar miniaturas automaticamente quando você envia imagens ou figurinhas. Instale `jimp` ou `sharp` — você não precisa dos dois. + + + +```bash jimp +npm install jimp +``` + +```bash sharp +npm install sharp +``` + + + +`sharp` é geralmente mais rápido para cargas de produção. `jimp` é uma implementação pura em JavaScript sem dependências nativas, o que facilita a instalação em ambientes restritos. + +### Pré-visualização de links + +Para gerar pré-visualizações ricas ao enviar URLs, instale `link-preview-js`: + +```bash +npm install link-preview-js +``` + +### Miniaturas de vídeo + +A geração de miniaturas para mensagens de vídeo requer `ffmpeg` instalado como dependência do sistema. Instale pelo gerenciador de pacotes do seu sistema operacional: + + + +```bash macOS +brew install ffmpeg +``` + +```bash Linux +sudo apt-get install ffmpeg +``` + +```bash Windows +winget install ffmpeg +``` + + + +### Decodificação de áudio + +O pacote `audio-decode` é uma peer dependency opcional usada para certas operações de processamento de áudio: + +```bash +npm install audio-decode +``` + +## Resumo das peer dependencies + +| Pacote | Finalidade | Obrigatório | +| --- | --- | --- | +| `jimp` | Miniaturas automáticas para imagens e figurinhas | Não | +| `sharp` | Alternativa mais rápida ao `jimp` para miniaturas | Não | +| `link-preview-js` | Pré-visualizações ricas de links em mensagens | Não | +| `ffmpeg` | Geração de miniaturas para mensagens de vídeo | Não | +| `audio-decode` | Suporte ao processamento de áudio | Não | + + + Para a maioria dos projetos, comece sem nenhuma peer dependency. Adicione `jimp` ou `sharp` quando precisar de miniaturas de imagem, e `link-preview-js` quando quiser pré-visualizações de URL. + + +## Próximos passos + + + + Conecte ao WhatsApp e envie sua primeira mensagem. + + + Vincule sua conta do WhatsApp com QR code ou código de pareamento. + + diff --git a/pt-BR/introduction.mdx b/pt-BR/introduction.mdx new file mode 100644 index 0000000..b01e3ae --- /dev/null +++ b/pt-BR/introduction.mdx @@ -0,0 +1,49 @@ +--- +title: "Baileys: biblioteca de API do WhatsApp Web para Node.js" +description: "Baileys conecta ao WhatsApp Web por WebSocket, sem navegador. Construa bots, automatize conversas e processe eventos em tempo real com TypeScript." +--- + +Baileys é uma biblioteca TypeScript baseada em WebSocket que permite interagir com a API do WhatsApp Web diretamente — sem navegador, sem Selenium, sem Chromium. Ela fala o mesmo protocolo binário Noise/protobuf do WhatsApp Web, então você pode autenticar, enviar e receber mensagens, gerenciar grupos e reagir a eventos em tempo real a partir de um processo Node.js leve. + +## O que você pode construir + +Com o Baileys, você pode criar bots de atendimento, sistemas de notificação, ferramentas de gerenciamento de grupos, automações de conversa e qualquer integração que precise enviar ou receber mensagens do WhatsApp programaticamente. A biblioteca expõe toda a superfície do protocolo do WhatsApp Web, então você não fica limitado a um conjunto fixo de ações. + +## Principais capacidades + +- **Envio e recebimento de mensagens** — texto, imagens, vídeo, áudio, documentos, figurinhas, enquetes, reações, localizações e contatos +- **Manipulação de mídia** — upload e download em streaming sem carregar arquivos inteiros na memória; geração automática de miniaturas com dependências opcionais +- **Grupos** — criar grupos, gerenciar participantes, atualizar metadados, processar pedidos de entrada e configurar mensagens efêmeras +- **Controles de privacidade** — ler e atualizar visto por último, foto de perfil, status, recibos de leitura e privacidade de adição em grupos +- **Presença** — assinar e transmitir indicadores de digitação e status online +- **Eventos em tempo real** — interface de EventEmitter tipada cobrindo mensagens, estado de conexão, contatos, conversas e mudanças de grupo +- **Persistência de sessão** — salvar e restaurar o estado de autenticação para que você só precise escanear o QR code uma vez +- **TypeScript primeiro** — todos os tipos públicos são exportados; suporte completo de IntelliSense no VS Code e editores compatíveis + +## Requisitos + +- Node.js >=20.0.0 — verificado no momento da instalação pelo campo `engines` em `package.json` +- TypeScript é suportado de fábrica; o pacote já vem com arquivos de declaração `.d.ts` + +## Aviso legal + + + Baileys não é afiliado, endossado ou oficialmente conectado ao WhatsApp ou Meta. "WhatsApp" e marcas relacionadas são marcas registradas dos respectivos donos. O uso desta biblioteca é por sua conta e risco. Os mantenedores não compactuam com spam, mensagens em massa, stalkerware ou qualquer uso que viole os Termos de Serviço do WhatsApp. + + +## Próximos passos + + + + Adicione o Baileys ao seu projeto Node.js com npm ou yarn, incluindo dependências opcionais. + + + Conecte ao WhatsApp, processe eventos e envie sua primeira mensagem em minutos. + + + Vincule sua conta do WhatsApp usando QR code ou código de pareamento. + + + Aprenda como o Baileys expõe estado de conexão, mensagens e mudanças de grupo como eventos tipados. + + diff --git a/pt-BR/messaging/chat-management.mdx b/pt-BR/messaging/chat-management.mdx new file mode 100644 index 0000000..6bfef0b --- /dev/null +++ b/pt-BR/messaging/chat-management.mdx @@ -0,0 +1,155 @@ +--- +title: "Gerenciar conversas: arquivar, silenciar, fixar e deletar" +description: "Use chatModify para arquivar, silenciar, marcar como lido, fixar, favoritar ou deletar conversas e mensagens." +--- + +`sock.chatModify(modification, jid)` envia atualizações de app-state cifradas. + + + Se você enviar um `chatModify` malformado, o WhatsApp pode te deslogar de todos os aparelhos. Sempre passe o `lastMessages` correto. + + +## Arquivar uma conversa + +```typescript +const lastMsgInChat = await getLastMessageInChat(jid) +await sock.chatModify({ archive: true, lastMessages: [lastMsgInChat] }, jid) +``` + +## Silenciar / dessilenciar + +| Duração | Milissegundos | +| --- | --- | +| Dessilenciar | `null` | +| 8 horas | `28800000` | +| 7 dias | `604800000` | + +```typescript +await sock.chatModify({ mute: 8 * 60 * 60 * 1000 }, jid) +await sock.chatModify({ mute: null }, jid) +``` + +## Marcar como lida ou não lida + +```typescript +const lastMsgInChat = await getLastMessageInChat(jid) +await sock.chatModify({ markRead: false, lastMessages: [lastMsgInChat] }, jid) +``` + +## Deletar mensagem só para você + +```typescript +await sock.chatModify( + { + clear: { + messages: [ + { + id: 'ATWYHDNNWU81732J', + fromMe: true, + timestamp: '1654823909' + } + ] + } + }, + jid +) +``` + +## Deletar uma conversa + +```typescript +const lastMsgInChat = await getLastMessageInChat(jid) +await sock.chatModify({ + delete: true, + lastMessages: [ + { + key: lastMsgInChat.key, + messageTimestamp: lastMsgInChat.messageTimestamp + } + ] + }, + jid +) +``` + +## Fixar / desfixar conversa + +```typescript +await sock.chatModify({ + pin: true + }, + jid +) +``` + +## Favoritar / desfavoritar mensagem + +```typescript +await sock.chatModify({ + star: { + messages: [ + { + id: 'messageID', + fromMe: true + } + ], + star: true + } + }, + jid +) +``` + +## Consultas de usuário + +### Verificar se um JID existe no WhatsApp + +```typescript +const [result] = await sock.onWhatsApp(jid) +if (result.exists) console.log (`${jid} exists on WhatsApp, as jid: ${result.jid}`) +``` + +### Consultar histórico + +```typescript +const msg = await getOldestMessageInChat(jid) +await sock.fetchMessageHistory( + 50, + msg.key, + msg.messageTimestamp +) +``` + + + Mensagens chegam pelo evento `messaging-history.set`, não retornadas diretamente. + + +### Buscar texto de status + +```typescript +const status = await sock.fetchStatus(jid) +console.log('status: ' + status) +``` + +### Buscar foto de perfil + +```typescript +const ppUrl = await sock.profilePictureUrl(jid) +const ppUrl = await sock.profilePictureUrl(jid, 'image') // alta resolução +``` + +### Buscar perfil business + +```typescript +const profile = await sock.getBusinessProfile(jid) +console.log('business description: ' + profile.description + ', category: ' + profile.category) +``` + +## Atualizar seu perfil + +```typescript +await sock.updateProfileStatus('Hello World!') +await sock.updateProfileName('My name') +await sock.updateProfilePicture(jid, { url: './new-profile-picture.jpeg' }) +await sock.removeProfilePicture(jid) +``` diff --git a/pt-BR/messaging/media-messages.mdx b/pt-BR/messaging/media-messages.mdx new file mode 100644 index 0000000..cc73f0b --- /dev/null +++ b/pt-BR/messaging/media-messages.mdx @@ -0,0 +1,146 @@ +--- +title: "Envie e receba mídia no WhatsApp" +description: "Envie imagens, vídeos, áudio, documentos e GIFs com o Baileys." +--- + +O Baileys aceita mídia em três formas via `WAMediaUpload`: `Buffer`, `{ url: '...' }`, ou `{ stream: Stream }`. + +```typescript +Buffer +{ url: URL | string } +{ stream: Readable } +``` + + + Prefira `{ url }` ou `{ stream }` ao invés de `Buffer` cru — o Baileys faz streaming direto e reduz uso de RAM. + + +## Enviando mídia + +### Imagem + +```typescript +await sock.sendMessage( + id, + { + image: { + url: './Media/ma_img.png' + }, + caption: 'hello word' + } +) +``` + +### Vídeo + +```typescript +await sock.sendMessage( + id, + { + video: { + url: './Media/ma_gif.mp4' + }, + caption: 'hello word', + ptv: false + } +) +``` + +### GIF + +O WhatsApp não suporta `.gif`. Envie como `.mp4` com `gifPlayback: true`. + +```typescript +await sock.sendMessage( + jid, + { + video: fs.readFileSync('Media/ma_gif.mp4'), + caption: 'hello word', + gifPlayback: true + } +) +``` + +### Áudio + +Converta com `ffmpeg` antes de enviar: + +```bash +ffmpeg -i input.mp4 -avoid_negative_ts make_zero -ac 1 output.ogg +``` + +```typescript +await sock.sendMessage( + jid, + { + audio: { + url: './Media/audio.mp3' + }, + mimetype: 'audio/mp4' + } +) +``` + + + Flags `ffmpeg`: `codec: libopus`, `ac: 1`, `avoid_negative_ts make_zero`. + + +### Visualizar uma vez + +```typescript +await sock.sendMessage( + id, + { + image: { + url: './Media/ma_img.png' + }, + viewOnce: true, + caption: 'hello word' + } +) +``` + +## Geração de miniaturas + +| Tipo de mídia | Dependência | Comando | +| --- | --- | --- | +| Imagens, figurinhas | `jimp` ou `sharp` | `yarn add jimp` / `yarn add sharp` | +| Vídeos | `ffmpeg` (sistema) | Gerenciador de pacotes do SO | + +## Baixando mídia recebida + +```typescript +import { createWriteStream } from 'fs' +import { downloadMediaMessage, getContentType } from '@whiskeysockets/baileys' + +sock.ev.on('messages.upsert', async ({ messages }) => { + for (const m of messages) { + if (!m.message) continue + const messageType = getContentType(m.message) + + if (messageType === 'imageMessage') { + const stream = await downloadMediaMessage( + m, + 'stream', + { }, + { + logger, + reuploadRequest: sock.updateMediaMessage + } + ) + const writeStream = createWriteStream('./my-download.jpeg') + stream.pipe(writeStream) + } + } +}) +``` + +## Re-upload de mídia antiga + +```typescript +await sock.updateMediaMessage(msg) +``` + + + `reuploadRequest: sock.updateMediaMessage` em `downloadMediaMessage` cuida disso automaticamente em 404 ou 410. + diff --git a/pt-BR/messaging/message-actions.mdx b/pt-BR/messaging/message-actions.mdx new file mode 100644 index 0000000..4ca7ca6 --- /dev/null +++ b/pt-BR/messaging/message-actions.mdx @@ -0,0 +1,57 @@ +--- +title: "Editar, deletar e ler mensagens do WhatsApp" +description: "Delete mensagens para todos, edite mensagens enviadas, marque como lidas e atualize sua presença." +--- + +## Deletar mensagem para todos + +```typescript +const msg = await sock.sendMessage(jid, { text: 'hello word' }) +await sock.sendMessage(jid, { delete: msg.key }) +``` + + + Para deletar só para você, use `sock.chatModify` com `clear`. Veja [gerenciar conversas](/pt-BR/messaging/chat-management). + + +## Editar mensagem + +```typescript +await sock.sendMessage(jid, { + text: 'updated text goes here', + edit: response.key, + }); +``` + +## Marcar como lida + +O Baileys exige marcar keys individuais — não dá para marcar uma conversa inteira. + +```typescript +const key: WAMessageKey +await sock.readMessages([key]) +``` + +## Atualizar presença + +```typescript +await sock.sendPresenceUpdate('available', jid) +``` + +| Valor | Significado | +| --- | --- | +| `available` | Online | +| `unavailable` | Offline | +| `composing` | Digitando | +| `recording` | Gravando áudio | +| `paused` | Parou de digitar | + + + Atualizações de presença expiram em ~10 segundos. Para sustentar "digitando…", envie repetidamente. + + + + Marque o Baileys como offline com `sock.sendPresenceUpdate('unavailable')` se quiser pushes no celular enquanto o bot roda. + + +Para chamadas, veja [Lidar com chamadas do WhatsApp](/pt-BR/advanced/calls). diff --git a/pt-BR/messaging/sending-messages.mdx b/pt-BR/messaging/sending-messages.mdx new file mode 100644 index 0000000..c4607f0 --- /dev/null +++ b/pt-BR/messaging/sending-messages.mdx @@ -0,0 +1,185 @@ +--- +title: "Envie mensagens do WhatsApp com o Baileys" +description: "Envie texto, links, contatos, localizações, reações, enquetes e mensagens fixadas usando sock.sendMessage." +--- + +Toda mensagem no Baileys passa por um único método: `sock.sendMessage(jid, content, options?)`. + +```typescript +sock.sendMessage(jid, content, options) +``` + + + Veja a lista completa de tipos no [type alias `AnyMessageContent`](https://baileys.wiki/docs/api/type-aliases/AnyMessageContent/) e todas as opções em [`MiscMessageGenerationOptions`](https://baileys.wiki/docs/api/type-aliases/MiscMessageGenerationOptions/). + + +## Mensagens não-mídia + +### Mensagem de texto + +```typescript +await sock.sendMessage(jid, { text: 'hello word' }) +``` + +### Citação / resposta + +```typescript +await sock.sendMessage(jid, { text: 'hello word' }, { quoted: message }) +``` + +### Mencionar um usuário + +```typescript +await sock.sendMessage( + jid, + { + text: '@12345678901', + mentions: ['12345678901@s.whatsapp.net'] + } +) +``` + +### Encaminhar uma mensagem + +```typescript +const msg = getMessageFromStore() +await sock.sendMessage(jid, { forward: msg }) +``` + +### Localização + +```typescript +await sock.sendMessage( + jid, + { + location: { + degreesLatitude: 24.121231, + degreesLongitude: 55.1121221 + } + } +) +``` + +### Contato (vCard) + +```typescript +const vcard = 'BEGIN:VCARD\n' + + 'VERSION:3.0\n' + + 'FN:Jeff Singh\n' + + 'ORG:Ashoka Uni;\n' + + 'TEL;type=CELL;type=VOICE;waid=911234567890:+91 12345 67890\n' + + 'END:VCARD' + +await sock.sendMessage( + id, + { + contacts: { + displayName: 'Jeff', + contacts: [{ vcard }] + } + } +) +``` + +### Reação + +```typescript +await sock.sendMessage( + jid, + { + react: { + text: '💖', + key: message.key + } + } +) +``` + +### Fixar mensagem + +| Duração | Segundos | +| --- | --- | +| 24 horas | `86400` | +| 7 dias | `604800` | +| 30 dias | `2592000` | + +```typescript +await sock.sendMessage( + jid, + { + pin: { + type: 1, + time: 86400, + key: message.key + } + } +) +``` + +### Enquete + +```typescript +await sock.sendMessage( + jid, + { + poll: { + name: 'My Poll', + values: ['Option 1', 'Option 2'], + selectableCount: 1, + toAnnouncementGroup: false + } + } +) +``` + + + Votos são cifrados. Para lê-los, escute `messages.update` e use `getAggregateVotesInPollMessage`. + + +### Pré-visualização de links + +```bash +yarn add link-preview-js +``` + +```typescript +await sock.sendMessage( + jid, + { + text: 'Hi, this was sent using https://github.com/whiskeysockets/baileys' + } +) +``` + +### Mensagens que desaparecem + +| Duração | Segundos | +| --- | --- | +| Remover | `0` | +| 24 horas | `86400` | +| 7 dias | `604800` | +| 90 dias | `7776000` | + + + + ```typescript + await sock.sendMessage( + jid, + { disappearingMessagesInChat: WA_DEFAULT_EPHEMERAL } + ) + ``` + + + ```typescript + await sock.sendMessage(jid, { text: 'hello' }, { ephemeralExpiration: WA_DEFAULT_EPHEMERAL }) + ``` + + + ```typescript + await sock.sendMessage( + jid, + { disappearingMessagesInChat: false } + ) + ``` + + diff --git a/pt-BR/migration/v7.mdx b/pt-BR/migration/v7.mdx new file mode 100644 index 0000000..ed0a908 --- /dev/null +++ b/pt-BR/migration/v7.mdx @@ -0,0 +1,101 @@ +--- +title: "Migrar para o Baileys v7" +description: "Mudanças incompatíveis no Baileys 7.x: LIDs, ACKs removidos, builds ESM, protobufs reduzidos e suporte ao Meta Coexistence." +--- + +## LIDs (Linked Identity JIDs) + +O WhatsApp finalizou o rollout de LID em 2024. Um **LIDJID** (Linked Identity Jabber Identifier) é um identificador por usuário no servidor `@lid` que anonimiza números em grupos grandes, em contraste ao **PNJID** legado (Phone Number Jabber Identifier) em `@s.whatsapp.net`. Por padrão, todas as novas sessões Signal no Baileys 7.x são criadas em LID. Veja [JIDs do WhatsApp explicados](/pt-BR/concepts/jids#pn--lid-o-modelo-de-identidade-dupla). + + + O sistema LID exige que seu auth state suporte as chaves `lid-mapping`, `device-list` e `tctoken`. + + +Pontos-chave: + +- **Um LID é um JID.** Único por usuário, não por grupo. +- **Você consegue resolver PN → LID, mas não o inverso.** O Baileys expõe `sock.signalRepository.lidMapping` com `storeLIDPNMapping`, `getLIDForPN`, `getLIDsForPNs` e `getPNForLID`. +- **`onWhatsApp` não retorna mais LIDs.** Use `getLIDForPN` / `getLIDsForPNs`. +- **`isJidUser` foi removido** em favor de `isPnUser`. + +### Mudanças em MessageKey + +- `remoteJidAlt` — JID alternativo para mensagens diretas +- `participantAlt` — JID alternativo para grupos, transmissões e canais + +### Mudanças em GroupMetadata + +Todo campo de ID tem um par PN: `owner` é LID; `ownerPn` tem o PN. `descOwner` / `descOwnerPn`, etc. + +### Mudanças em Contact + +`Contact` não tem mais `jid` e `lid` separados: + +- `id` é o identificador preferido. +- `phoneNumber` populado quando `id` é LID. +- `lid` populado quando `id` é PN. + +### Compartilhamento de número + +- Empresas: `{ requestPhoneNumber: true }` +- Usuários: `{ sharePhoneNumber: true }` + +### Novo evento + +`lid-mapping.update` dispara quando um par PN ↔ LID novo é descoberto. + +### Novo enum + +`WAMessageAddressingMode`. + + + Não tente "restaurar" PN JIDs. Migre para LIDs. + + +## ACKs não são mais enviados + +O WhatsApp tem banido contas que enviam ACKs em entregas com sucesso — desligar é o padrão seguro agora. + +## Meta Coexistence + +O Baileys 7.x consegue enviar, receber e parear com contas com Coexistence. Suporte experimental — reporte issues. + +## Apenas ESM + +Baileys 7.x é só ESM. + +### Opção 1 — Converter para ESM (recomendado) + +Defina `"type": "module"` no `package.json` e troque `require()` por `import`. + +### Opção 2 — Importar dinamicamente do CommonJS + +```ts +const fs = require('fs') + +async function loadESMModule() { + try { + const { default: makeWASocket } = await import('@whiskeysockets/baileys') + const socket = makeWASocket({ /* ... */ }) + } catch (error) { + console.error('Error loading ESM module:', error) + } +} + +loadESMModule() +``` + +O projeto também migrou para Yarn v4. Requer `corepack`. + +## Protobufs mais enxutos + +Restantes: + +- `.create()` — em vez de `.fromObject()`. +- `.encode()` e `.decode()`. + +Use `BufferJSON` ao (de)serializar protos. Use `decodeAndHydrate()` em vez de `decode()` puro. + +## Referência + +[GitHub releases](https://github.com/WhiskeySockets/Baileys/releases). Link curto: [whiskey.so/migrate-latest](https://whiskey.so/migrate-latest). diff --git a/pt-BR/migration/v8.mdx b/pt-BR/migration/v8.mdx new file mode 100644 index 0000000..4464132 --- /dev/null +++ b/pt-BR/migration/v8.mdx @@ -0,0 +1,26 @@ +--- +title: "Migrar para o Baileys v8" +description: "O que esperar no Baileys v8: arquitetura baseada em classes, integração com whatsmeow e novo sistema de autenticação." +--- + + + O Baileys v8 está em desenvolvimento ativo. Detalhes podem mudar antes do release. + + +## O que há de novo + +- **Arquitetura baseada em classes.** v8 substitui o factory atual por classes. +- **Integração com whatsmeow.** v8 é a base para adotar código do projeto [whatsmeow](https://github.com/tulir/whatsmeow). +- **Novo sistema de autenticação.** Você precisa rodar o helper de migração antes de subir o runtime. + +## Antes de atualizar + +1. Faça backup do auth state. +2. Rode o helper de migração v8 num script separado. +3. Após todos migrados, faça deploy do runtime v8. + +Clientes não migrados não conectam no v8. + +## Referência + +[GitHub releases](https://github.com/WhiskeySockets/Baileys/releases). Link curto: [whiskey.so/migrate-latest](https://whiskey.so/migrate-latest). diff --git a/pt-BR/quickstart.mdx b/pt-BR/quickstart.mdx new file mode 100644 index 0000000..53080f4 --- /dev/null +++ b/pt-BR/quickstart.mdx @@ -0,0 +1,185 @@ +--- +title: "Comece com o Baileys" +description: "Construa seu primeiro bot de WhatsApp com o Baileys em minutos. Aprenda a conectar, processar eventos, salvar sessões e enviar sua primeira mensagem." +--- + +Este guia mostra como criar uma aplicação Baileys mínima que conecta ao WhatsApp, persiste sua sessão, escuta mensagens recebidas e responde a elas. Ao final, você terá um bot funcional que pode estender com sua própria lógica. + + + O Baileys é uma biblioteca não oficial e não é afiliada ao WhatsApp. Use-o com responsabilidade e em conformidade com os Termos de Serviço do WhatsApp. Os mantenedores não compactuam com mensagens em massa, spam ou stalkerware. + + +## Exemplo completo + +Os passos abaixo levam a este script funcional completo. Use-o como ponto de partida para o seu projeto. + +```typescript +import makeWASocket, { DisconnectReason, useMultiFileAuthState } from '@whiskeysockets/baileys' +import { Boom } from '@hapi/boom' +import qrcode from 'qrcode-terminal' + +async function connectToWhatsApp() { + const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys') + + const sock = makeWASocket({ + auth: state + }) + + sock.ev.on('connection.update', (update) => { + const { connection, lastDisconnect, qr } = update + if (qr) { + qrcode.generate(qr, { small: true }) + } + if (connection === 'close') { + const shouldReconnect = + (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut + console.log('connection closed due to', lastDisconnect?.error, ', reconnecting:', shouldReconnect) + if (shouldReconnect) { + connectToWhatsApp() + } + } else if (connection === 'open') { + console.log('opened connection') + } + }) + + sock.ev.on('messages.upsert', async (event) => { + for (const m of event.messages) { + console.log(JSON.stringify(m, undefined, 2)) + + console.log('replying to', m.key.remoteJid) + await sock.sendMessage(m.key.remoteJid!, { text: 'Hello from Baileys!' }) + } + }) + + sock.ev.on('creds.update', saveCreds) +} + +connectToWhatsApp() +``` + +## Passo a passo + + + + Adicione o Baileys e seu peer `@hapi/boom` ao projeto. `@hapi/boom` é uma dependência direta do Baileys, usada para inspecionar códigos de erro de desconexão. + + + + ```bash npm + npm install @whiskeysockets/baileys @hapi/boom + ``` + + ```bash yarn + yarn add @whiskeysockets/baileys @hapi/boom + ``` + + + + Garanta que você está rodando **Node.js 20.0.0 ou superior**. Caso contrário, a instalação falhará. + + + + O Baileys precisa de um objeto de estado de autenticação para gerenciar credenciais de sessão e chaves do Signal Protocol. O utilitário interno `useMultiFileAuthState` salva tudo em uma pasta local. + + ```typescript + import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys' + + const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys') + ``` + + `state` contém as credenciais e chaves atuais. `saveCreds` é um callback que você passa ao evento `creds.update`, para que a sessão seja gravada em disco sempre que mudar. + + + O nome da pasta (`auth_info_baileys` neste exemplo) pode ser qualquer caminho que você quiser. Guarde-o fora do código-fonte e adicione ao `.gitignore` — ele contém chaves criptográficas de longa duração da sua conta do WhatsApp. + + + + + Crie um socket chamando `makeWASocket` com seu estado de autenticação. Escute o campo `qr` em `connection.update` e renderize você mesmo — `printQRInTerminal` está obsoleto. + + ```typescript + import qrcode from 'qrcode-terminal' + + const sock = makeWASocket({ + auth: state + }) + + sock.ev.on('connection.update', ({ qr }) => { + if (qr) qrcode.generate(qr, { small: true }) + }) + ``` + + + Quando você executar pela primeira vez, um QR code aparece no terminal. Abra o WhatsApp no celular, vá em **Configurações → Aparelhos conectados → Conectar um aparelho** e escaneie o código. Em execuções subsequentes, as credenciais salvas são reutilizadas e nenhum QR code é mostrado. + + + + + Escute `connection.update` para reagir quando a conexão abrir, fechar ou encontrar erros. A lógica de reconexão abaixo reinicia o socket automaticamente — exceto quando você foi explicitamente deslogado. + + ```typescript + import { DisconnectReason } from '@whiskeysockets/baileys' + import { Boom } from '@hapi/boom' + + sock.ev.on('connection.update', (update) => { + const { connection, lastDisconnect } = update + if (connection === 'close') { + const shouldReconnect = + (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut + console.log('connection closed due to', lastDisconnect?.error, ', reconnecting:', shouldReconnect) + if (shouldReconnect) { + connectToWhatsApp() + } + } else if (connection === 'open') { + console.log('opened connection') + } + }) + ``` + + `DisconnectReason.loggedOut` significa que sua sessão foi revogada. Nesse caso, apague a pasta de autenticação e escaneie um novo QR code. + + + + O evento `messages.upsert` dispara sempre que novas mensagens chegam. Itere sobre `event.messages` para tratar cada uma. + + ```typescript + sock.ev.on('messages.upsert', async (event) => { + for (const m of event.messages) { + console.log(JSON.stringify(m, undefined, 2)) + + console.log('replying to', m.key.remoteJid) + await sock.sendMessage(m.key.remoteJid!, { text: 'Hello from Baileys!' }) + } + }) + ``` + + `m.key.remoteJid` é o ID do WhatsApp (JID) da conversa de origem da mensagem. Passe-o como primeiro argumento de `sock.sendMessage` para responder. + + + + Registre o callback `saveCreds` no evento `creds.update`. O Baileys o chama sempre que suas credenciais de sessão mudam. + + ```typescript + sock.ev.on('creds.update', saveCreds) + ``` + + Se pular este passo, sua sessão não será salva e você precisará escanear o QR novamente a cada reinicialização. + + + +## E agora? + + + + Use código de pareamento em vez de QR code, ou aprenda a gerenciar sessões em banco de dados. + + + Envie texto, imagens, vídeo, áudio, enquetes, reações e mais. + + + Veja a lista completa de eventos que o Baileys emite. + + + Crie e gerencie grupos, processe pedidos de entrada e configure mensagens efêmeras. + +