plogme 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,35 @@
1
- # plogme
2
-
3
- `plogme` is a Node.js WhatsApp Web client based on the Baileys protocol implementation. It supports authentication, media, groups, interactive messages, status updates, albums, native flows, rich responses, and experimental Meta AI-style message envelopes.
4
-
5
- > **Experimental feature notice:** Rich responses, Meta AI-style metadata, HTML primitives, clipboard addon sections, carousels, and albums use WhatsApp Web protocol structures that may change without notice. Unsupported fields can be ignored, rejected, or rendered differently across WhatsApp versions.
1
+ # Plogme
2
+
3
+ **Plogme** is a Node.js WhatsApp Web client built on the Baileys protocol implementation. It provides authentication, messaging, media uploads, groups, status updates, interactive messages, albums, rich responses, Meta AI-style envelopes, HTML screens, and selected WhatsApp Web protocol features.
4
+
5
+ > [!IMPORTANT]
6
+ > Plogme is a protocol library. WhatsApp server behavior and client rendering can change without notice. Rich responses, HTML primitives, native flows, reels, carousels, and experimental protocol fields may render differently across WhatsApp versions.
7
+
8
+ [![npm version](https://img.shields.io/npm/v/plogme?style=flat-square)](https://www.npmjs.com/package/plogme)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-10b981?style=flat-square&logo=node.js)](https://nodejs.org/)
10
+ [![Tests](https://img.shields.io/badge/tests-30%20passing-10b981?style=flat-square)](https://github.com/crysnovax/baileys)
11
+
12
+ ## Contents
13
+
14
+ 1. [Installation](#installation)
15
+ 2. [Quick start](#quick-start)
16
+ 3. [Authentication and pairing](#authentication-and-pairing)
17
+ 4. [Sending messages](#sending-messages)
18
+ 5. [Media and albums](#media-and-albums)
19
+ 6. [Interactive messages](#interactive-messages)
20
+ 7. [Rich messages](#rich-messages)
21
+ 8. [Rich menus, reels, and grids](#rich-menus-reels-and-grids)
22
+ 9. [HTML screens and built-in apps](#html-screens-and-built-in-apps)
23
+ 10. [Status updates](#status-updates)
24
+ 11. [Groups and group history](#groups-and-group-history)
25
+ 12. [Reports and moderation](#reports-and-moderation)
26
+ 13. [Experimental bot metadata](#experimental-bot-metadata)
27
+ 14. [WhatsApp flows and web views](#whatsapp-flows-and-web-views)
28
+ 15. [View-once utilities](#view-once-utilities)
29
+ 16. [Connection handling](#connection-handling)
30
+ 17. [Security and removed automatic behavior](#security-and-removed-automatic-behavior)
31
+ 18. [Development](#development)
32
+ 19. [License and support](#license-and-support)
6
33
 
7
34
  ## Installation
8
35
 
@@ -10,29 +37,32 @@
10
37
  npm install plogme
11
38
  ```
12
39
 
13
- The current package version is **2.0.0**. Use Node.js 20 or newer.
40
+ The package currently requires **Node.js 20 or newer**.
14
41
 
15
- ## Important: Join Update Channels Manually
42
+ To install the development branch directly from GitHub:
16
43
 
17
- **Plogme does not automatically follow or subscribe to any WhatsApp channel.** If you want to receive code examples, tutorials, updates, and usage guidance, please join the relevant official channel manually:
44
+ ```bash
45
+ npm install github:crysnovax/baileys
46
+ ```
18
47
 
19
- - **CODY AI:** [Join the CODY AI WhatsApp channel](https://whatsapp.com/channel/0029Vb8296789inZCpWLuS3T)
20
- - **CRYSNOVA AI:** [Join the CRYSNOVA AI WhatsApp channel](https://whatsapp.com/channel/0029Vb6pe77K0IBn48HLKb38)
48
+ Plogme is an ES module package. Use `import` syntax in Node.js projects configured with `"type": "module"`.
21
49
 
22
- Joining either channel is optional and is never performed automatically by the package.
50
+ > [!WARNING]
51
+ > Never commit authentication state, private keys, `.env` files, or downloaded session data. Add your session directory to `.gitignore`.
23
52
 
24
53
  ## Quick start
25
54
 
55
+ This is the smallest complete connection example. It saves credentials, reconnects after temporary disconnects, and stops reconnecting after logout.
56
+
26
57
  ```js
27
- import {
28
- makeWASocket,
58
+ import makeWASocket, {
29
59
  DisconnectReason,
30
60
  useMultiFileAuthState
31
61
  } from 'plogme'
32
62
  import { Boom } from '@hapi/boom'
33
63
  import pino from 'pino'
34
64
 
35
- async function start() {
65
+ async function connect() {
36
66
  const { state, saveCreds } = await useMultiFileAuthState('./session')
37
67
  const sock = makeWASocket({
38
68
  auth: state,
@@ -40,38 +70,60 @@ async function start() {
40
70
  })
41
71
 
42
72
  sock.ev.on('creds.update', saveCreds)
73
+
43
74
  sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
44
- if (connection === 'open') console.log('Connected')
45
- if (connection === 'close') {
46
- const status = new Boom(lastDisconnect?.error)?.output?.statusCode
47
- if (status !== DisconnectReason.loggedOut) start()
75
+ if (connection === 'open') {
76
+ console.log('Connected to WhatsApp')
77
+ return
48
78
  }
79
+
80
+ if (connection !== 'close') return
81
+
82
+ const statusCode = new Boom(lastDisconnect?.error)?.output?.statusCode
83
+ if (statusCode === DisconnectReason.loggedOut) {
84
+ console.error('Logged out. Remove the old session and pair again.')
85
+ return
86
+ }
87
+
88
+ console.log('Connection closed; reconnecting')
89
+ connect().catch(console.error)
49
90
  })
91
+
92
+ return sock
50
93
  }
51
94
 
52
- start()
95
+ await connect()
53
96
  ```
54
97
 
55
- Keep authentication files private. Never commit session credentials, private keys, or `.env` files.
56
-
57
98
  ## Authentication and pairing
58
99
 
100
+ ### Multi-file authentication
101
+
102
+ `useMultiFileAuthState` is the recommended development setup because credentials and keys are stored as separate files.
103
+
59
104
  ```js
60
105
  const { state, saveCreds } = await useMultiFileAuthState('./session')
61
-
62
106
  const sock = makeWASocket({ auth: state })
63
107
  sock.ev.on('creds.update', saveCreds)
108
+ ```
109
+
110
+ ### Pairing code
64
111
 
112
+ Request a pairing code after creating the socket. Supply a phone number in international format without `+`, spaces, or punctuation.
113
+
114
+ ```js
65
115
  const code = await sock.requestPairingCode('15551234567')
66
- console.log(code)
116
+ console.log('Pairing code:', code)
67
117
  ```
68
118
 
69
- Pairing codes require the socket to be initialized and a valid phone number in international format.
119
+ Keep the pairing code private and use it only with the WhatsApp account you control.
120
+
121
+ ## Sending messages
70
122
 
71
- ## Ordinary text, mentions, reactions, and media
123
+ ### Text, mentions, and reactions
72
124
 
73
125
  ```js
74
- await sock.sendMessage(jid, { text: 'Hello!' })
126
+ await sock.sendMessage(jid, { text: 'Hello from Plogme' })
75
127
 
76
128
  await sock.sendMessage(jid, {
77
129
  text: 'Hello @15551234567',
@@ -79,11 +131,112 @@ await sock.sendMessage(jid, {
79
131
  })
80
132
 
81
133
  await sock.sendMessage(jid, {
82
- react: { text: '👍', key: message.key }
134
+ react: {
135
+ text: '👍',
136
+ key: message.key
137
+ }
83
138
  })
84
139
  ```
85
140
 
86
- Media examples:
141
+ ### WhatsApp Channels
142
+
143
+ Resolve a Channel invite code or a full WhatsApp Channel invite URL without
144
+ automatically following the Channel:
145
+
146
+ ```js
147
+ const channel = await sock.newsletterGetInviteInfo(
148
+ 'https://whatsapp.com/channel/0029Vb6pe77K0IBn48HLKb38'
149
+ )
150
+
151
+ console.log(channel?.id, channel?.name, channel?.description)
152
+ ```
153
+
154
+ React to a Channel post with its Channel JID and `serverId`. Pass `null`,
155
+ `undefined`, or an empty string to remove the reaction:
156
+
157
+ ```js
158
+ await sock.newsletterReactMessage(
159
+ '120363000000000000@newsletter',
160
+ '12345',
161
+ '👍'
162
+ )
163
+
164
+ await sock.newsletterReactMessage(
165
+ '120363000000000000@newsletter',
166
+ '12345',
167
+ null
168
+ )
169
+ ```
170
+
171
+ ### Chat themes and animated wallpapers
172
+
173
+ Send a chat-theme protocol message with a color scheme and an animated
174
+ wallpaper. `chatThemeId` is normalized to the WhatsApp color-scheme form by
175
+ adding `@Tonal`; use `colorSchemeId` when you already have the complete value.
176
+ `dimLevel` is a normalized value from `0` to `1`.
177
+
178
+ ```js
179
+ await sock.sendMessage(jid, {
180
+ chatTheme: {
181
+ chatThemeId: 'Royal-Blue',
182
+ animatedWallpaper: {
183
+ id: 'wallpaper-1',
184
+ dimLevel: 0.25
185
+ }
186
+ }
187
+ })
188
+
189
+ // Equivalent explicit color-scheme form:
190
+ await sock.sendMessage(jid, {
191
+ chatTheme: {
192
+ colorSchemeId: 'Royal-Blue@Tonal',
193
+ animatedWallpaper: {
194
+ animatedWallpaperId: 'wallpaper-1',
195
+ opacity: 25
196
+ }
197
+ }
198
+ })
199
+ ```
200
+
201
+ ### Editing and deleting
202
+
203
+ ```js
204
+ await sock.sendMessage(jid, {
205
+ text: 'Updated text',
206
+ edit: message.key
207
+ })
208
+
209
+ await sock.sendMessage(jid, {
210
+ caption: 'Updated media caption',
211
+ edit: message.key
212
+ })
213
+
214
+ await sock.sendMessage(jid, {
215
+ delete: message.key
216
+ })
217
+ ```
218
+
219
+ ### Forwarding
220
+
221
+ ```js
222
+ await sock.sendMessage(jid, {
223
+ forward: message,
224
+ force: true
225
+ })
226
+ ```
227
+
228
+ ### Profile name and About status
229
+
230
+ These methods update the connected account's own profile.
231
+
232
+ ```js
233
+ await sock.updateProfileName('Cody AI')
234
+ await sock.updateProfileStatus('Building helpful WhatsApp tools')
235
+ ```
236
+
237
+ ## Media and albums
238
+
239
+ ### Images, video, audio, documents, and stickers
87
240
 
88
241
  ```js
89
242
  await sock.sendMessage(jid, {
@@ -94,161 +247,245 @@ await sock.sendMessage(jid, {
94
247
  await sock.sendMessage(jid, {
95
248
  video: { url: './video.mp4' },
96
249
  caption: 'Video caption',
97
- gifPlayback: false
250
+ gifPlayback: false,
251
+ ptv: false
252
+ })
253
+
254
+ await sock.sendMessage(jid, {
255
+ audio: { url: './voice.ogg' },
256
+ mimetype: 'audio/ogg; codecs=opus',
257
+ ptt: true
98
258
  })
99
259
 
100
260
  await sock.sendMessage(jid, {
101
261
  document: { url: './report.pdf' },
102
262
  mimetype: 'application/pdf',
103
- fileName: 'report.pdf'
263
+ fileName: 'report.pdf',
264
+ caption: 'Monthly report'
104
265
  })
105
266
 
106
267
  await sock.sendMessage(jid, {
107
- audio: { url: './audio.ogg' },
108
- mimetype: 'audio/ogg; codecs=opus'
268
+ sticker: { url: './sticker.webp' }
109
269
  })
110
270
  ```
111
271
 
112
- ## Profile name and About description
272
+ Remote URLs, local file paths, and buffers are supported where the selected media helper accepts them.
273
+
274
+ > [!WARNING]
275
+ > A URL must point to a real, playable media file. A URL returning an HTML error page, an empty file, or an invalid video container can produce a WhatsApp media verification or download warning even when the library code is correct.
113
276
 
114
- WhatsApp exposes the personal profile description as the **About status**. In
115
- `plogme`, use `updateProfileStatus`—not `updateProfileDescription`—and pass a
116
- plain string:
277
+ ### GIF playback and video notes
117
278
 
118
279
  ```js
119
- await sock.updateProfileStatus('Building helpful bots')
120
- await sock.updateProfileName('Cody AI')
121
- ```
280
+ await sock.sendMessage(jid, {
281
+ video: { url: './animation.mp4' },
282
+ gifPlayback: true,
283
+ caption: 'Animated clip'
284
+ })
122
285
 
123
- This updates the connected account's own profile. It does not update a contact
124
- or group description.
286
+ await sock.sendMessage(jid, {
287
+ video: { url: './video-note.mp4' },
288
+ ptv: true
289
+ })
290
+ ```
125
291
 
126
- ## Spoiler messages
292
+ ### Albums
127
293
 
128
- For media, use `spoiler: true`. The default native mode serializes the
129
- supported `contextInfo.isSpoiler` flag:
294
+ Use at least two image or video items. Albums are sent as a parent message followed by separately encrypted media children.
130
295
 
131
296
  ```js
132
- await sock.sendMessage(jid, {
133
- image: { url: './classified.jpg' },
134
- caption: 'Open only when ready',
135
- spoiler: true
297
+ await sock.sendAlbumMessage(jid, [
298
+ { type: 'image', data: { url: './one.jpg' } },
299
+ { type: 'image', data: { url: './two.jpg' } }
300
+ ], {
301
+ caption: 'Trip photos'
136
302
  })
137
303
  ```
138
304
 
139
- For clients that do not support native media spoilers, explicitly use the
140
- compatibility fallback. This sends a view-once envelope; it is **not** the
141
- same as a native spoiler:
305
+ The general `sendMessage` album form is also supported:
142
306
 
143
307
  ```js
144
308
  await sock.sendMessage(jid, {
145
- video: { url: './reveal.mp4' },
146
- spoiler: true,
147
- mediaSpoilerMode: 'viewOnce'
309
+ album: [
310
+ { image: { url: './one.jpg' }, caption: 'First' },
311
+ { video: { url: './clip.mp4' }, caption: 'Second' }
312
+ ]
148
313
  })
149
314
  ```
150
315
 
151
- For text, the default fallback uses WhatsApp's rolling-out `||...||` spoiler
152
- formatting. Native text metadata can be requested with
153
- `spoilerMode: 'native'`. Rendering remains client-version dependent.
316
+ Rich-response and bot-forwarded messages cannot be nested as album children. Send them as separate messages.
317
+
318
+ ## Interactive messages
319
+
320
+ ### Buttons and lists
154
321
 
155
322
  ```js
156
323
  await sock.sendMessage(jid, {
157
- text: 'The ending is revealed here',
158
- spoiler: true
324
+ text: 'Choose an option',
325
+ footer: 'Plogme',
326
+ buttons: [
327
+ { text: 'Confirm', id: 'confirm' },
328
+ { text: 'Cancel', id: 'cancel' }
329
+ ]
159
330
  })
160
- ```
161
331
 
162
- ## Rich text and Markdown
332
+ await sock.sendMessage(jid, {
333
+ text: 'Choose a category',
334
+ title: 'Categories',
335
+ buttonText: 'Open list',
336
+ sections: [
337
+ {
338
+ title: 'Available categories',
339
+ rows: [
340
+ { title: 'Support', description: 'Get help', rowId: 'support' },
341
+ { title: 'News', description: 'Read updates', rowId: 'news' }
342
+ ]
343
+ }
344
+ ]
345
+ })
346
+ ```
163
347
 
164
- `sendRichText` sends Markdown-capable text inside a forwarded GenAI rich-response envelope. Markdown rendering depends on the recipient's WhatsApp client.
348
+ ### Native flow
165
349
 
166
350
  ```js
167
- await sock.sendRichText(jid, {
168
- text: `**Heading**
351
+ await sock.sendWhatsAppFlow(jid, {
352
+ flowId: 'your-flow-id',
353
+ flowToken: 'your-flow-token',
354
+ cta: 'Open form',
355
+ text: 'Complete this form'
356
+ })
357
+ ```
169
358
 
170
- Normal text
359
+ For a rich URL call-to-action:
171
360
 
172
- > This is a blockquote`
361
+ ```js
362
+ await sock.sendRichWebview(jid, {
363
+ url: 'https://example.com/app',
364
+ text: 'Open the app',
365
+ buttonText: 'Open',
366
+ useWebview: true
173
367
  })
174
368
  ```
175
369
 
176
- A dedicated blockquote helper is also available:
370
+ ### Interactive table
177
371
 
178
372
  ```js
179
- await sock.sendMarkdownQuote(jid, {
180
- text: `Here is the instruction:
181
-
182
- > Use this exact logo image.`
373
+ await sock.sendInteractiveTable(jid, {
374
+ title: 'Deployment status',
375
+ headers: ['Service', 'State'],
376
+ rows: [
377
+ ['API', 'Online'],
378
+ ['Worker', 'Online']
379
+ ],
380
+ buttons: [],
381
+ footer: 'Updated now'
183
382
  })
184
383
  ```
185
384
 
186
- ## Copy-to-clipboard controls
385
+ ## Rich messages
187
386
 
188
- There are two different clipboard-related features:
387
+ Rich messages are forwarded GenAI-style envelopes. The sender now routes rich payloads through normal WhatsApp message generation before relay so clients receive the expected message metadata.
189
388
 
190
- 1. `sendCopyButton` creates the inline GenAI `COPY_TO_CLIPBOARD` addon section.
191
- 2. `sendRichTextCopy` and `sendQuoteCopy` place ordinary Markdown content and a clipboard addon section in the same rich response. The text does **not** need to contain a quote.
389
+ > [!NOTE]
390
+ > Rich rendering is client-dependent. A rich message can be structurally valid while a particular WhatsApp build chooses a fallback rendering.
192
391
 
193
- Normal text with a copy action:
392
+ ### Rich text and quotes
194
393
 
195
394
  ```js
196
- await sock.sendRichTextCopy(jid, {
197
- text: 'npm install plogme',
198
- copyText: 'npm install plogme',
199
- copyLabel: 'Copy command'
395
+ await sock.sendRichText(jid, {
396
+ text: '**Deployment complete**\n\nThe service is online.'
397
+ })
398
+
399
+ await sock.sendMarkdownQuote(jid, {
400
+ text: '> Use the production URL for verification.'
200
401
  })
201
402
  ```
202
403
 
203
- The same behavior can be requested through `sendRichText` by supplying `copyText`:
404
+ ### Copy-to-clipboard actions
204
405
 
205
406
  ```js
206
- await sock.sendRichText(jid, {
207
- text: 'This is ordinary text with a copy action.',
208
- copyText: 'This is ordinary text with a copy action.',
209
- copyLabel: 'Copy me'
407
+ await sock.sendCopyButton(jid, {
408
+ text: 'npm install plogme',
409
+ label: 'Copy command'
210
410
  })
211
- ```
212
411
 
213
- Quote plus copy action:
412
+ await sock.sendRichTextCopy(jid, {
413
+ text: 'Install the package first.',
414
+ copyText: 'npm install plogme',
415
+ copyLabel: 'Copy command'
416
+ })
214
417
 
215
- ```js
216
418
  await sock.sendQuoteCopy(jid, {
217
- text: `Ready-to-paste instruction:
218
-
219
- > Use this exact logo image...`,
220
- copyText: 'Use this exact logo image...',
221
- copyLabel: 'Copy me'
419
+ text: '> Run the tests before publishing.',
420
+ copyText: 'npm test',
421
+ copyLabel: 'Copy test command'
222
422
  })
223
423
  ```
224
424
 
225
- `COPY_TO_CLIPBOARD` is an in-message rich-response action. It is different from the separate copy/forward/like/dislike toolbar that WhatsApp may render below first-party Meta AI messages. That toolbar is client-generated and is not authoritatively controllable through this API.
226
-
227
- ## Rich links, images, HTML, and menus
425
+ ### Rich image and inline links
228
426
 
229
427
  ```js
230
- await sock.sendRichLink(jid, {
231
- text: 'Open the deployment site',
232
- url: 'https://example.com'
233
- })
234
-
235
428
  await sock.sendRichImage(jid, {
236
429
  url: 'https://example.com/image.jpg',
237
430
  mimeType: 'image/jpeg'
238
431
  })
239
432
 
240
- await sock.sendHtmlMessage(jid, {
241
- html: '<h1>Hello</h1><p>Rendered HTML content</p>'
433
+ await sock.sendRichLink(jid, {
434
+ text: 'Open the documentation',
435
+ links: [
436
+ {
437
+ text: 'Documentation',
438
+ url: 'https://example.com/docs',
439
+ isTrusted: true
440
+ }
441
+ ]
242
442
  })
243
443
  ```
244
444
 
245
- `sendHtmlMessage` uses an experimental HTML primitive. Do not pass untrusted user input into HTML without sanitizing it.
445
+ ### Rich tables, lists, code, and LaTeX
246
446
 
247
- The experimental rich menu helper accepts a header, body buttons, cards, and footer:
447
+ ```js
448
+ await sock.sendTable(
449
+ jid,
450
+ 'Build status',
451
+ ['Service', 'State'],
452
+ [
453
+ { items: ['API', 'Online'] },
454
+ { items: ['Worker', 'Online'] }
455
+ ]
456
+ )
457
+
458
+ await sock.sendList(jid, 'Next steps', [
459
+ { title: 'Test', description: 'Run the test suite' },
460
+ { title: 'Deploy', description: 'Publish the verified build' }
461
+ ])
462
+
463
+ await sock.sendCodeBlock(jid, 'console.log("hello")', undefined, {
464
+ language: 'javascript'
465
+ })
466
+
467
+ await sock.sendRichAIResponse(jid, {
468
+ table: {
469
+ title: 'Status',
470
+ headers: ['Name', 'Value'],
471
+ rows: [['version', '2.0.1']]
472
+ }
473
+ })
474
+ ```
475
+
476
+ `sendRichAIResponse` also accepts `list`, `codeBlock`, and `latex` content objects. For a pre-existing Meta AI response, `captureAndResendUnifiedResponse(jid, message, quoted)` captures and resends its unified response.
477
+
478
+ ## Rich menus, reels, and grids
479
+
480
+ ### Original Gen4 rich menu
481
+
482
+ Use `sock.richMenu` for the original Gen4-style menu. This is the primary rich-menu API.
248
483
 
249
484
  ```js
250
485
  await sock.richMenu(jid, {
251
- header: { title: 'Deploy Suite' },
486
+ header: {
487
+ title: 'Deployment tools'
488
+ },
252
489
  body: {
253
490
  title: 'Choose an action',
254
491
  buttons: [
@@ -256,136 +493,150 @@ await sock.richMenu(jid, {
256
493
  { id: 'logs', text: 'View logs' }
257
494
  ]
258
495
  },
259
- footer: { text: 'Example menu' }
496
+ footer: {
497
+ text: 'Open documentation',
498
+ url: 'https://example.com/docs'
499
+ }
260
500
  })
261
501
  ```
262
502
 
263
- Always validate button IDs on the server. Never treat a user-controlled button ID as an authorization grant.
503
+ Cards and carousel layout are supported:
264
504
 
265
- ## Carousels and rich button grids
505
+ ```js
506
+ await sock.richMenu(jid, {
507
+ body: {
508
+ carousel: true,
509
+ cards: [
510
+ {
511
+ title: 'Production',
512
+ toast: 'Production selected',
513
+ buttons: [{ id: 'production', text: 'Open' }]
514
+ },
515
+ {
516
+ title: 'Staging',
517
+ toast: 'Staging selected',
518
+ buttons: [{ id: 'staging', text: 'Open' }]
519
+ }
520
+ ]
521
+ }
522
+ })
523
+ ```
266
524
 
267
- Use `sendRichButtonGrid` for media cards with native-flow buttons. A carousel needs at least one valid image, video, or product header per card. The helper accepts either `buttons` or `nativeFlow`.
525
+ Validate button IDs on the server. A button ID is an event value, not an authorization grant.
526
+
527
+ ### Reels and rich media
528
+
529
+ `sendReels` accepts an array of reel records. The main rich-media path is used for reels, sources, links, tables, and future rich payloads.
268
530
 
269
531
  ```js
270
- await sock.sendRichButtonGrid(jid, {
271
- text: 'Choose an option',
272
- footer: 'Select one',
273
- cards: [
274
- {
275
- image: { url: './first.jpg' },
276
- title: 'First card',
277
- caption: 'First card description',
278
- buttons: [
279
- { id: 'first', text: 'Select first' }
280
- ]
281
- },
282
- {
283
- image: { url: './second.jpg' },
284
- title: 'Second card',
285
- caption: 'Second card description',
286
- buttons: [
287
- { id: 'second', text: 'Select second' }
288
- ]
289
- }
290
- ]
532
+ await sock.sendReels(jid, [
533
+ {
534
+ title: 'Example reel',
535
+ creator: 'Example creator',
536
+ videoUrl: 'https://cdn.example.com/reel.mp4',
537
+ thumbnailUrl: 'https://cdn.example.com/reel.jpg',
538
+ profileIconUrl: 'https://cdn.example.com/avatar.jpg',
539
+ likesCount: 120,
540
+ sharesCount: 8,
541
+ viewCount: 2400,
542
+ reelSource: 'IG',
543
+ isVerified: true
544
+ }
545
+ ], undefined, {
546
+ text: 'Featured reels'
291
547
  })
292
548
  ```
293
549
 
294
- Supported button properties include `id`, `text`, `url`, `call`, and `copy`. Carousel cards must have valid media headers. A card containing only text is not a valid carousel card.
550
+ Use a valid MP4 or another supported playable video. Test the URL with a media probe before sending it.
295
551
 
296
- For a single native-flow message:
552
+ For a general rich response:
297
553
 
298
554
  ```js
299
- await sock.sendMessage(jid, {
300
- text: 'Open the site',
301
- nativeFlow: [
302
- { url: 'https://example.com', text: 'Open' }
555
+ await sock.sendRichMedia(jid, {
556
+ contentText: 'A rich response',
557
+ reels: [
558
+ {
559
+ title: 'Short clip',
560
+ url: 'https://cdn.example.com/clip.mp4'
561
+ }
303
562
  ]
304
563
  })
305
564
  ```
306
565
 
307
- ## Media albums
566
+ ### Rich button grid
308
567
 
309
- Albums are sent as a parent album message followed by associated image or video child messages. Provide at least two media items:
568
+ `sendRichButtonGrid` sends ordinary message cards with native-flow buttons. It is distinct from the original Gen4 `richMenu` envelope.
310
569
 
311
570
  ```js
312
- await sock.sendMessage(jid, {
313
- album: [
571
+ await sock.sendRichButtonGrid(jid, {
572
+ text: 'Choose a service',
573
+ footer: 'Select one',
574
+ cards: [
314
575
  {
315
- image: { url: './image-1.jpg' },
316
- caption: 'First image'
576
+ title: 'Status',
577
+ image: { url: './status.jpg' },
578
+ buttons: [{ text: 'View status', id: 'status' }]
317
579
  },
318
580
  {
319
- image: { url: './image-2.jpg' },
320
- caption: 'Second image'
581
+ title: 'Logs',
582
+ image: { url: './logs.jpg' },
583
+ buttons: [{ text: 'View logs', id: 'logs' }]
321
584
  }
322
585
  ]
323
586
  })
324
587
  ```
325
588
 
326
- Album children are required to be actual `imageMessage` or `videoMessage` payloads. A `botForwardedMessage` or `richResponseMessage` cannot be nested as an album child. If rich text or a rich-response envelope is needed, send it as a separate message before or after the album.
589
+ ## HTML screens and built-in apps
327
590
 
328
- ## Experimental bot metadata
329
-
330
- The following methods attach newer `WAProto` bot metadata fields through `messageContextInfo.botMetadata`. They are experimental and may have no visible effect unless the receiving client and server support the corresponding first-party feature.
331
-
332
- ### Diagnostics metadata
591
+ ### Ordinary HTML message
333
592
 
334
593
  ```js
335
- await sock.sendBotDiagnostics(jid, {
336
- text: 'Diagnostics test',
337
- botBackend: 1, // 0 = AAPI, 1 = CLIPPY
338
- toolsUsed: ['web_search', 'image_search'],
339
- isThinking: true
594
+ await sock.sendHtmlMessage(jid, {
595
+ html: '<h1>Hello</h1><p>Rendered HTML content.</p>',
596
+ trustedSources: []
340
597
  })
341
598
  ```
342
599
 
343
- ### Command metadata
600
+ ### Tabbed rich HTML screen
344
601
 
345
602
  ```js
346
- await sock.sendBotCommand(jid, {
347
- text: 'Command metadata test',
348
- commandName: 'summarize',
349
- commandDescription: 'Summarize the current conversation',
350
- commandPrompt: 'Summarize the current conversation in five bullets'
603
+ await sock.sendRichHtmlMessage(jid, {
604
+ title: 'Example app',
605
+ url: 'https://your-domain.example',
606
+ trustedSources: ['your-domain.example'],
607
+ tabs: [
608
+ {
609
+ tab_header: 'Home',
610
+ html: '<h1>Home</h1><p>Welcome.</p>'
611
+ },
612
+ {
613
+ tab_header: 'Help',
614
+ html: '<h2>Help</h2><p>Tap the buttons to continue.</p>'
615
+ }
616
+ ]
351
617
  })
352
618
  ```
353
619
 
354
- ### Resolved tool-call metadata
620
+ `trustedSources` and `url` are application-specific. Sanitize user-controlled HTML before sending it.
355
621
 
356
- ```js
357
- await sock.sendBotToolResult(jid, {
358
- text: 'Tool result test',
359
- toolCallId: `tool-${Date.now()}`,
360
- resolutionData: {
361
- status: 'success',
362
- result: { value: 'Completed' }
363
- }
364
- })
365
- ```
366
-
367
- You may provide a pre-serialized result instead:
622
+ ### Built-in screens
368
623
 
369
624
  ```js
370
- await sock.sendBotToolResult(jid, {
371
- text: 'Serialized result',
372
- toolCallId: 'tool-123',
373
- resolutionDataSerialized: JSON.stringify({
374
- status: 'success',
375
- value: 'Completed'
376
- })
377
- })
625
+ await sock.sendSlotMachine(jid)
626
+ await sock.sendMiniApp(jid, { title: 'Pairing mini-app' })
627
+ await sock.sendWebsitePreview(jid, 'https://example.com')
628
+ await sock.sendLinkPreview(jid, 'https://example.com')
378
629
  ```
379
630
 
380
- These methods do not fabricate Meta's verification certificates or signatures. They should not be treated as a way to impersonate a first-party Meta AI response.
631
+ The built-in mini-app uses its configured first-party source. Generic rich HTML remains configurable.
381
632
 
382
633
  ## Status updates
383
634
 
384
- Use `backgroundColor` rather than `backgroundArgb` in the high-level API:
635
+ `sendStatus` sends a status update and resolves recipients from `statusJidList`.
385
636
 
386
637
  ```js
387
638
  await sock.sendStatus({
388
- text: 'Service status: online',
639
+ text: 'Service online',
389
640
  backgroundColor: 0xff102a43,
390
641
  font: 2
391
642
  }, {
@@ -393,198 +644,156 @@ await sock.sendStatus({
393
644
  })
394
645
  ```
395
646
 
396
- Alternatively, send directly to `status@broadcast` when using the raw status protocol supported by the installed version.
647
+ For status mentions:
648
+
649
+ ```js
650
+ await sock.sendStatusMention(
651
+ { text: 'Welcome to the team' },
652
+ ['15551234567@s.whatsapp.net']
653
+ )
654
+ ```
397
655
 
398
- ## Groups
656
+ ## Groups and group history
399
657
 
400
658
  ```js
401
659
  const metadata = await sock.groupMetadata(groupJid)
660
+
402
661
  await sock.groupParticipantsUpdate(groupJid, [userJid], 'add')
403
662
  await sock.groupParticipantsUpdate(groupJid, [userJid], 'remove')
404
663
  await sock.groupSettingUpdate(groupJid, 'announcement')
405
664
  ```
406
665
 
407
- Group administration requires the corresponding privileges. Handle permission failures and disconnected sessions.
666
+ ### Member labels
408
667
 
409
- ### Group member labels
410
-
411
- `updateMemberLabel` sets the connected account's own custom member label in a
412
- group. The first argument must be the **group JID** (`123456789-123456@g.us`),
413
- not the member's individual JID:
668
+ `updateMemberLabel` sets the connected account's own label in a group.
414
669
 
415
670
  ```js
416
671
  await sock.updateMemberLabel(groupJid, 'Developer')
672
+ await sock.updateMemberLabel(groupJid, '') // remove the label
417
673
  ```
418
674
 
419
- To remove the label, pass an empty string:
675
+ ### Group history sharing
420
676
 
421
677
  ```js
422
- await sock.updateMemberLabel(groupJid, '')
678
+ await sock.toggleGroupHistory(groupJid, true) // all members
679
+ await sock.toggleGroupHistory(groupJid, false) // admins only
423
680
  ```
424
681
 
425
- Labels are limited to 30 characters by the protocol. This is not an admin
426
- operation for assigning labels to arbitrary participants; WhatsApp associates
427
- the label with the account that sends the protocol message.
682
+ ## Reports and moderation
428
683
 
429
- ## WhatsApp Flows and web views
684
+ The reporting APIs have different wire operations. Use the method that matches the action you intend to perform.
430
685
 
431
686
  ```js
432
- await sock.sendWhatsAppFlow(jid, {
433
- flowId: '1234567890',
434
- flowToken: 'example-token',
435
- cta: 'Open flow',
436
- text: 'Complete this form'
437
- })
687
+ await sock.reportSpam(
688
+ groupJid,
689
+ [{ id: messageId, from: userJid, t: Math.floor(Date.now() / 1000) }],
690
+ 'group_info_report',
691
+ 'Example group'
692
+ )
438
693
 
439
- await sock.sendRichWebview(jid, {
440
- url: 'https://example.com/app',
441
- text: 'Open the app',
442
- buttonText: 'Open'
443
- })
694
+ const userAccepted = await sock.reportUser(userJid, 'spam')
695
+ const messageAccepted = await sock.reportMessage(
696
+ userJid,
697
+ messageId,
698
+ 'harassment',
699
+ participantJid
700
+ )
701
+ await sock.reportAndBlockUser(userJid, 'scam')
444
702
  ```
445
703
 
446
- The flow ID, token, and hosted URL must be valid for the target business configuration.
704
+ `reportUser` and `reportMessage` return whether WhatsApp acknowledged the request. An acknowledgement is not a guarantee of a particular moderation outcome.
447
705
 
448
- ## Message options
706
+ ## Experimental bot metadata
707
+
708
+ These methods attach experimental fields under `messageContextInfo.botMetadata`.
449
709
 
450
710
  ```js
451
- await sock.sendMessage(jid, {
452
- text: 'Disappearing message',
453
- ephemeralExpiration: 86400
711
+ await sock.sendBotDiagnostics(jid, {
712
+ text: 'Diagnostics test',
713
+ botBackend: 1,
714
+ toolsUsed: ['web_search'],
715
+ isThinking: false
454
716
  })
455
717
 
456
- await sock.sendMessage(jid, {
457
- text: 'Updated text',
458
- edit: message.key
718
+ await sock.sendBotCommand(jid, {
719
+ text: 'Command test',
720
+ commandName: 'summarize',
721
+ commandDescription: 'Summarize the conversation',
722
+ commandPrompt: 'Summarize the conversation in five bullets'
459
723
  })
460
724
 
461
- await sock.sendMessage(jid, {
462
- delete: message.key
725
+ await sock.sendBotToolResult(jid, {
726
+ text: 'Tool completed',
727
+ toolCallId: 'tool-123',
728
+ resolutionData: { status: 'success', value: 42 }
463
729
  })
464
730
  ```
465
731
 
466
- Raw protocol fields are version-dependent. Unsupported fields may be ignored or rejected by WhatsApp.
467
-
468
- ## Error handling and reconnection
732
+ These methods do not provide Meta's private verification credentials and must not be used to impersonate a first-party Meta AI message.
469
733
 
470
- Connections may close because of logout, network interruption, protocol updates, or server-side session invalidation. Avoid starting multiple concurrent connection loops.
734
+ ## WhatsApp flows and web views
471
735
 
472
736
  ```js
473
- sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
474
- if (connection !== 'close') return
475
-
476
- const status = new Boom(lastDisconnect?.error)?.output?.statusCode
477
- if (status === DisconnectReason.loggedOut) {
478
- console.error('Logged out; remove the old session and pair again.')
479
- return
480
- }
737
+ await sock.sendWhatsAppFlow(jid, {
738
+ flowId: 'your-flow-id',
739
+ flowToken: 'your-flow-token',
740
+ cta: 'Open form',
741
+ text: 'Complete this form'
742
+ })
481
743
 
482
- console.log('Connection interrupted; reconnect with backoff.')
744
+ await sock.sendRichWebview(jid, {
745
+ url: 'https://example.com/app',
746
+ text: 'Open the app',
747
+ buttonText: 'Open'
483
748
  })
484
749
  ```
485
750
 
486
- ## Reporting, group history, rich HTML, and source grep
487
-
488
- Version 1.0.6 adds the realvare-style `w:report` methods without replacing plogme's existing evidence-based `reportSpam` method. The methods use different WhatsApp IQ namespaces and should be selected according to the operation you intend to perform.
751
+ Flow identifiers and tokens must belong to a valid business configuration.
489
752
 
490
- | API | Wire operation | Use |
491
- | --- | --- | --- |
492
- | `reportSpam(jid, messages, spamFlow, subject)` | `xmlns="spam"` → `spam_list` | Report a chat or group with message evidence. |
493
- | `reportUser(jid, reason, waitForAckMs)` | `xmlns="w:report"` → `user` | Report one user. |
494
- | `reportMessage(jid, messageId, reason, participantJid, waitForAckMs)` | `xmlns="w:report"` → `message` | Report one message. |
495
- | `reportAndBlockUser(jid, reason, waitForAckMs)` | `w:report` followed by block | Report a user and then block them. |
753
+ ## View-once utilities
496
754
 
497
755
  ```js
498
- // Existing plogme spam-list report with evidence.
499
- await sock.reportSpam(
500
- '1203630@g.us',
501
- [{ id: 'ABCD1234', from: '15550001111@s.whatsapp.net', t: 1710000000 }],
502
- 'group_info_report',
503
- 'Example group'
504
- )
756
+ const info = sock.getViewOnceInfo(message)
757
+ console.log(info)
505
758
 
506
- // New direct report methods.
507
- const acknowledged = await sock.reportUser('15550001111@s.whatsapp.net', 'spam')
508
- const messageAcknowledged = await sock.reportMessage(
509
- '15550001111@s.whatsapp.net', 'ABCD1234', 'harassment',
510
- '15550001111@s.whatsapp.net'
511
- )
512
- await sock.reportAndBlockUser('15550001111@s.whatsapp.net', 'scam')
759
+ if (sock.isViewOnce(message)) {
760
+ await sock.autoVV(message)
761
+ }
513
762
  ```
514
763
 
515
- `reportUser` and `reportMessage` return `true` when WhatsApp acknowledges the IQ request and `false` when it times out. A timeout does not prove delivery failed; the request may already have reached the server.
764
+ `autoVV` and `antiVV` are utilities for view-once handling. Their behavior depends on the message shape and client version; use them only for messages you are authorized to process.
516
765
 
517
- ### Group history sharing
766
+ ## Connection handling
518
767
 
519
- `toggleGroupHistory(jid, true)` selects `ALL_MEMBER_SHARE`; passing `false` selects `ADMIN_SHARE` through the WhatsApp MEX endpoint:
768
+ A connection can close because of logout, network interruption, protocol changes, or session invalidation. Avoid starting multiple reconnect loops for the same socket.
520
769
 
521
770
  ```js
522
- await sock.toggleGroupHistory('1203630@g.us', true)
523
- await sock.toggleGroupHistory('1203630@g.us', false)
524
- ```
525
-
526
- ### Two HTML transports
527
-
528
- `sendHtmlMessage` sends the ordinary single-screen `FOAHtmlPrimitiveDemoDONOTUSE` envelope. `sendRichHtmlMessage` is the reusable transport for interactive GenAI HTML screens like tabbed games; it uses `GenAIaeacdsnwHtmlPrimitive` inside `FOAIDNixelButtonSheets` and supports multiple tabs.
771
+ sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
772
+ if (connection !== 'close') return
529
773
 
530
- ```js
531
- await sock.sendHtmlMessage(jid, {
532
- html: '<h1>Simple screen</h1><p>One HTML payload.</p>',
533
- trustedSources: []
534
- })
774
+ const statusCode = new Boom(lastDisconnect?.error)?.output?.statusCode
775
+ if (statusCode === DisconnectReason.loggedOut) {
776
+ console.error('Logged out; pair again with a new session')
777
+ return
778
+ }
535
779
 
536
- await sock.sendRichHtmlMessage(jid, {
537
- title: 'Games',
538
- url: 'https://your-domain.example',
539
- trustedSources: ['your-domain.example'],
540
- tabs: [
541
- { tab_header: 'mini-app', html: '<canvas id="game"></canvas><script>/* game */</script>' },
542
- { tab_header: 'Instructions', html: '<h2>How to play</h2><p>Tap to jump.</p>' }
543
- ]
780
+ setTimeout(() => {
781
+ connect().catch(console.error)
782
+ }, 3000)
544
783
  })
545
784
  ```
546
785
 
547
- The rich transport is deliberately separate because clients that render the normal HTML primitive do not necessarily open the embedded-screen/tabbed envelope the same way.
548
-
549
- The built-in `sendSlotMachine` keeps its existing ordinary HTML transport. The attached interactive mini-app is available as `sendMiniApp`; because it uses the embedded-screen envelope, plogme configures that built-in game with the owned `pair.crysnova.link` source:
550
-
551
- ```js
552
- await sock.sendMiniApp(jid)
553
- await sock.sendMiniApp(jid, { title: 'My mini-app' })
554
- ```
555
-
556
- The `sendRichHtmlMessage` function itself remains neutral: user-supplied screens do not inherit `pair.crysnova.link` and should provide their own `url` and `trustedSources` when those fields are needed.
557
-
558
- ### Reusable source grep
559
-
560
- `grepSource` is exported from `lib/Utils/index.js`, and `sock.grepCode` is available after socket creation. It returns exact relative file paths, one-based line numbers, columns, and matching source lines without invoking a shell.
561
-
562
- ```js
563
- const matches = sock.grepCode('sendRichHtmlMessage', { maxResults: 20 })
564
- console.log(matches)
565
- // [{ file: 'lib/Socket/messages-send.js', line: 2705, column: 12, text: '...' }]
566
-
567
- const reportMatches = sock.grepCode('reportMessage', { caseSensitive: false })
568
- const regexMatches = sock.grepCode('report(User|Message)', { regex: true, flags: 'i' })
569
- ```
570
-
571
- By default the search covers `lib` and `WAProto`; `includeReadme: true` also searches the README.
572
-
573
- ## Changes to Automatic Behaviors
574
-
575
- Previous versions of Plogme included several related behaviors that have been removed from the npm release:
786
+ ## Security and removed automatic behavior
576
787
 
577
- - Automatic following of the project's official WhatsApp channel.
578
- - Anti-rebranding/anti-brand enforcement.
579
- - Device fingerprinting and remote attestation, which collected machine characteristics such as the hostname, operating system, CPU model, architecture, and Node.js version before sending a fingerprint to an external verification service.
788
+ Plogme does not automatically follow WhatsApp channels, fingerprint the host machine, contact a remote attestation service, or enforce rebranding through runtime code.
580
789
 
581
- These behaviors were not introduced with malicious intent. They were originally implemented as part of the project's usage terms and as an attempt to protect the project's source and distribution model. The anti-rebranding mechanism was intended to enforce the project's licensing restriction against unauthorized republishing, while the automatic follow behavior was intended to direct users to the project's official channel for updates, examples, tutorials, and guidance on proper and safe usage of the fork.
790
+ > [!NOTE]
791
+ > Official update channels are optional. Users who want updates must join them manually:
792
+ >
793
+ > - [CODY AI WhatsApp channel](https://whatsapp.com/channel/0029Vb8296789inZCpWLuS3T)
794
+ > - [CRYSNOVA AI WhatsApp channel](https://whatsapp.com/channel/0029Vb6pe77K0IBn48HLKb38)
582
795
 
583
- Users were previously informed about these behaviors and advised not to use the package if they did not agree with them.
584
-
585
- However, these mechanisms went beyond what should be expected from a library and could result in behavior that users did not explicitly initiate. For transparency, predictability, and a better npm package experience, these mechanisms have now been completely removed.
586
-
587
- Current Plogme releases do not automatically follow external accounts or channels, do not enforce branding or rebranding restrictions through runtime behavior, and do not fingerprint the host machine or contact a remote attestation service for that purpose. The project's licensing terms still apply; removing runtime enforcement does not grant permission to violate the applicable license.
796
+ The project license still applies. Removing runtime enforcement does not change the license terms.
588
797
 
589
798
  ## Development
590
799
 
@@ -593,80 +802,18 @@ npm install
593
802
  npm test
594
803
  ```
595
804
 
596
- Before publishing, verify generated protobuf files, package contents, version metadata, and that no authentication state or environment files are included.
597
-
598
- ## License
599
-
600
- Review the included license before distributing applications built with `plogme`. Keep required notices with redistributed source and binaries.
601
-
602
- ## Support
603
-
604
- For implementation questions, include the Node.js version, `plogme` version, operating system, relevant error text, and a minimal reproducible example. Never include authentication state or private account data in an issue.
805
+ Before publishing, verify the package contents and ensure no session data, private keys, or environment files are included.
605
806
 
807
+ The repository includes generated WhatsApp protocol files under `WAProto/`. Changes to generated protocol definitions should be made deliberately and tested against the installed WhatsApp client versions you support.
606
808
 
607
- ## Ported WhatsApp Web protocol features
608
-
609
- The bundled WAProto schema is updated to WhatsApp Web version `2.3000.1047412487`, with the generated runtime, TypeScript declarations, and connection version updated together. Selected support includes newsletter follower-invite base/V2 fields, ACP2 receive-state extraction, animated wallpaper encoding, and nested audio in sticker messages. These remain experimental and client-dependent.
610
-
611
- ### Animated wallpaper
612
-
613
- ```js
614
- await sock.sendMessage(jid, {
615
- chatTheme: {
616
- animatedWallpaper: { id: 'wallpaper-id', dimLevel: 0.25 }
617
- }
618
- })
619
- ```
620
-
621
- ### Sticker with audio
809
+ ## License and support
622
810
 
623
- ```js
624
- await sock.sendMessage(jid, {
625
- sticker: { url: './sticker.webp' },
626
- stickerAudio: { url: './sound.ogg' }
627
- })
628
- ```
811
+ Review [`LICENSE`](LICENSE) before redistributing applications or modified source.
629
812
 
630
- ### Newsletter follower invites
813
+ For implementation questions, include the Node.js version, Plogme version, operating system, relevant error text, and a minimal reproduction. Do not include authentication state or private account data.
631
814
 
632
- ```js
633
- await sock.sendMessage(jid, {
634
- newsletterFollowerInvite: {
635
- newsletterJid: '123456789@newsletter',
636
- newsletterName: 'My newsletter',
637
- caption: 'Follow this channel'
638
- }
639
- })
640
-
641
- await sock.sendMessage(jid, {
642
- newsletterFollowerInviteMessageV2: {
643
- newsletterJid: '123456789@newsletter',
644
- newsletterName: 'My newsletter',
645
- caption: 'Follow this channel'
646
- }
647
- })
648
- ```
649
-
650
- ### Device-scoped relay options
651
-
652
- `relayMessage` supports `isSecret`, `protected`, and `me` for supported private-chat device fan-out paths. These select recipients and may add `device_fanout`; they are not encryption, access-control, or delivery guarantees and may not behave identically for groups, statuses, retries, or future clients. They are unrelated to the protobuf field `messageContextInfo.messageSecret`.
653
-
654
- ```js
655
- await sock.relayMessage(jid, message, {
656
- messageId: messageId,
657
- isSecret: true
658
- })
659
- ```
660
-
661
- ### Explicit album wrapper
662
-
663
- Albums remain parent/child wire messages: one album header is relayed first, followed by separately encrypted image/video children associated with the parent key. The receiver must group them visually. The explicit wrapper accepts typed media objects and applies a caption to the first child:
664
-
665
- ```js
666
- await sock.sendAlbumMessage(jid, [
667
- { type: 'image', data: { url: './one.jpg' } },
668
- { type: 'image', data: { url: './two.jpg' } }
669
- ], { caption: 'Album caption' })
670
- ```
815
+ ## References
671
816
 
672
- Rich HTML and bot-forwarded messages cannot be album children; send those separately. Album grouping is still dependent on WhatsApp server/client behavior.
817
+ [1]: https://github.com/crysnovax/baileys "Plogme source repository"
818
+ [2]: https://www.npmjs.com/package/plogme "Plogme on npm"
819
+ [3]: https://github.com/WhiskeySockets/Baileys "Baileys protocol implementation"