plogme 1.0.6 → 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,20 +37,32 @@
10
37
  npm install plogme
11
38
  ```
12
39
 
13
- The current package version is **1.0.6**. Use Node.js 20 or newer.
40
+ The package currently requires **Node.js 20 or newer**.
41
+
42
+ To install the development branch directly from GitHub:
43
+
44
+ ```bash
45
+ npm install github:crysnovax/baileys
46
+ ```
47
+
48
+ Plogme is an ES module package. Use `import` syntax in Node.js projects configured with `"type": "module"`.
49
+
50
+ > [!WARNING]
51
+ > Never commit authentication state, private keys, `.env` files, or downloaded session data. Add your session directory to `.gitignore`.
14
52
 
15
53
  ## Quick start
16
54
 
55
+ This is the smallest complete connection example. It saves credentials, reconnects after temporary disconnects, and stops reconnecting after logout.
56
+
17
57
  ```js
18
- import {
19
- makeWASocket,
58
+ import makeWASocket, {
20
59
  DisconnectReason,
21
60
  useMultiFileAuthState
22
61
  } from 'plogme'
23
62
  import { Boom } from '@hapi/boom'
24
63
  import pino from 'pino'
25
64
 
26
- async function start() {
65
+ async function connect() {
27
66
  const { state, saveCreds } = await useMultiFileAuthState('./session')
28
67
  const sock = makeWASocket({
29
68
  auth: state,
@@ -31,38 +70,60 @@ async function start() {
31
70
  })
32
71
 
33
72
  sock.ev.on('creds.update', saveCreds)
73
+
34
74
  sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
35
- if (connection === 'open') console.log('Connected')
36
- if (connection === 'close') {
37
- const status = new Boom(lastDisconnect?.error)?.output?.statusCode
38
- if (status !== DisconnectReason.loggedOut) start()
75
+ if (connection === 'open') {
76
+ console.log('Connected to WhatsApp')
77
+ return
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
39
86
  }
87
+
88
+ console.log('Connection closed; reconnecting')
89
+ connect().catch(console.error)
40
90
  })
91
+
92
+ return sock
41
93
  }
42
94
 
43
- start()
95
+ await connect()
44
96
  ```
45
97
 
46
- Keep authentication files private. Never commit session credentials, private keys, or `.env` files.
47
-
48
98
  ## Authentication and pairing
49
99
 
100
+ ### Multi-file authentication
101
+
102
+ `useMultiFileAuthState` is the recommended development setup because credentials and keys are stored as separate files.
103
+
50
104
  ```js
51
105
  const { state, saveCreds } = await useMultiFileAuthState('./session')
52
-
53
106
  const sock = makeWASocket({ auth: state })
54
107
  sock.ev.on('creds.update', saveCreds)
108
+ ```
109
+
110
+ ### Pairing code
111
+
112
+ Request a pairing code after creating the socket. Supply a phone number in international format without `+`, spaces, or punctuation.
55
113
 
114
+ ```js
56
115
  const code = await sock.requestPairingCode('15551234567')
57
- console.log(code)
116
+ console.log('Pairing code:', code)
58
117
  ```
59
118
 
60
- 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
61
122
 
62
- ## Ordinary text, mentions, reactions, and media
123
+ ### Text, mentions, and reactions
63
124
 
64
125
  ```js
65
- await sock.sendMessage(jid, { text: 'Hello!' })
126
+ await sock.sendMessage(jid, { text: 'Hello from Plogme' })
66
127
 
67
128
  await sock.sendMessage(jid, {
68
129
  text: 'Hello @15551234567',
@@ -70,11 +131,112 @@ await sock.sendMessage(jid, {
70
131
  })
71
132
 
72
133
  await sock.sendMessage(jid, {
73
- react: { text: '👍', key: message.key }
134
+ react: {
135
+ text: '👍',
136
+ key: message.key
137
+ }
138
+ })
139
+ ```
140
+
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
+ }
74
198
  })
75
199
  ```
76
200
 
77
- Media examples:
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
78
240
 
79
241
  ```js
80
242
  await sock.sendMessage(jid, {
@@ -85,161 +247,245 @@ await sock.sendMessage(jid, {
85
247
  await sock.sendMessage(jid, {
86
248
  video: { url: './video.mp4' },
87
249
  caption: 'Video caption',
88
- 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
89
258
  })
90
259
 
91
260
  await sock.sendMessage(jid, {
92
261
  document: { url: './report.pdf' },
93
262
  mimetype: 'application/pdf',
94
- fileName: 'report.pdf'
263
+ fileName: 'report.pdf',
264
+ caption: 'Monthly report'
95
265
  })
96
266
 
97
267
  await sock.sendMessage(jid, {
98
- audio: { url: './audio.ogg' },
99
- mimetype: 'audio/ogg; codecs=opus'
268
+ sticker: { url: './sticker.webp' }
100
269
  })
101
270
  ```
102
271
 
103
- ## 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.
104
276
 
105
- WhatsApp exposes the personal profile description as the **About status**. In
106
- `plogme`, use `updateProfileStatus`—not `updateProfileDescription`—and pass a
107
- plain string:
277
+ ### GIF playback and video notes
108
278
 
109
279
  ```js
110
- await sock.updateProfileStatus('Building helpful bots')
111
- await sock.updateProfileName('Cody AI')
112
- ```
280
+ await sock.sendMessage(jid, {
281
+ video: { url: './animation.mp4' },
282
+ gifPlayback: true,
283
+ caption: 'Animated clip'
284
+ })
113
285
 
114
- This updates the connected account's own profile. It does not update a contact
115
- or group description.
286
+ await sock.sendMessage(jid, {
287
+ video: { url: './video-note.mp4' },
288
+ ptv: true
289
+ })
290
+ ```
116
291
 
117
- ## Spoiler messages
292
+ ### Albums
118
293
 
119
- For media, use `spoiler: true`. The default native mode serializes the
120
- 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.
121
295
 
122
296
  ```js
123
- await sock.sendMessage(jid, {
124
- image: { url: './classified.jpg' },
125
- caption: 'Open only when ready',
126
- 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'
127
302
  })
128
303
  ```
129
304
 
130
- For clients that do not support native media spoilers, explicitly use the
131
- compatibility fallback. This sends a view-once envelope; it is **not** the
132
- same as a native spoiler:
305
+ The general `sendMessage` album form is also supported:
133
306
 
134
307
  ```js
135
308
  await sock.sendMessage(jid, {
136
- video: { url: './reveal.mp4' },
137
- spoiler: true,
138
- mediaSpoilerMode: 'viewOnce'
309
+ album: [
310
+ { image: { url: './one.jpg' }, caption: 'First' },
311
+ { video: { url: './clip.mp4' }, caption: 'Second' }
312
+ ]
139
313
  })
140
314
  ```
141
315
 
142
- For text, the default fallback uses WhatsApp's rolling-out `||...||` spoiler
143
- formatting. Native text metadata can be requested with
144
- `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
145
321
 
146
322
  ```js
147
323
  await sock.sendMessage(jid, {
148
- text: 'The ending is revealed here',
149
- spoiler: true
324
+ text: 'Choose an option',
325
+ footer: 'Plogme',
326
+ buttons: [
327
+ { text: 'Confirm', id: 'confirm' },
328
+ { text: 'Cancel', id: 'cancel' }
329
+ ]
150
330
  })
151
- ```
152
331
 
153
- ## 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
+ ```
154
347
 
155
- `sendRichText` sends Markdown-capable text inside a forwarded GenAI rich-response envelope. Markdown rendering depends on the recipient's WhatsApp client.
348
+ ### Native flow
156
349
 
157
350
  ```js
158
- await sock.sendRichText(jid, {
159
- 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
+ ```
160
358
 
161
- Normal text
359
+ For a rich URL call-to-action:
162
360
 
163
- > 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
164
367
  })
165
368
  ```
166
369
 
167
- A dedicated blockquote helper is also available:
370
+ ### Interactive table
168
371
 
169
372
  ```js
170
- await sock.sendMarkdownQuote(jid, {
171
- text: `Here is the instruction:
172
-
173
- > 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'
174
382
  })
175
383
  ```
176
384
 
177
- ## Copy-to-clipboard controls
385
+ ## Rich messages
178
386
 
179
- 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.
180
388
 
181
- 1. `sendCopyButton` creates the inline GenAI `COPY_TO_CLIPBOARD` addon section.
182
- 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.
183
391
 
184
- Normal text with a copy action:
392
+ ### Rich text and quotes
185
393
 
186
394
  ```js
187
- await sock.sendRichTextCopy(jid, {
188
- text: 'npm install plogme',
189
- copyText: 'npm install plogme',
190
- 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.'
191
401
  })
192
402
  ```
193
403
 
194
- The same behavior can be requested through `sendRichText` by supplying `copyText`:
404
+ ### Copy-to-clipboard actions
195
405
 
196
406
  ```js
197
- await sock.sendRichText(jid, {
198
- text: 'This is ordinary text with a copy action.',
199
- copyText: 'This is ordinary text with a copy action.',
200
- copyLabel: 'Copy me'
407
+ await sock.sendCopyButton(jid, {
408
+ text: 'npm install plogme',
409
+ label: 'Copy command'
201
410
  })
202
- ```
203
411
 
204
- 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
+ })
205
417
 
206
- ```js
207
418
  await sock.sendQuoteCopy(jid, {
208
- text: `Ready-to-paste instruction:
209
-
210
- > Use this exact logo image...`,
211
- copyText: 'Use this exact logo image...',
212
- copyLabel: 'Copy me'
419
+ text: '> Run the tests before publishing.',
420
+ copyText: 'npm test',
421
+ copyLabel: 'Copy test command'
213
422
  })
214
423
  ```
215
424
 
216
- `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.
217
-
218
- ## Rich links, images, HTML, and menus
425
+ ### Rich image and inline links
219
426
 
220
427
  ```js
221
- await sock.sendRichLink(jid, {
222
- text: 'Open the deployment site',
223
- url: 'https://example.com'
224
- })
225
-
226
428
  await sock.sendRichImage(jid, {
227
429
  url: 'https://example.com/image.jpg',
228
430
  mimeType: 'image/jpeg'
229
431
  })
230
432
 
231
- await sock.sendHtmlMessage(jid, {
232
- 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
+ ]
233
442
  })
234
443
  ```
235
444
 
236
- `sendHtmlMessage` uses an experimental HTML primitive. Do not pass untrusted user input into HTML without sanitizing it.
445
+ ### Rich tables, lists, code, and LaTeX
237
446
 
238
- 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.
239
483
 
240
484
  ```js
241
485
  await sock.richMenu(jid, {
242
- header: { title: 'Deploy Suite' },
486
+ header: {
487
+ title: 'Deployment tools'
488
+ },
243
489
  body: {
244
490
  title: 'Choose an action',
245
491
  buttons: [
@@ -247,136 +493,150 @@ await sock.richMenu(jid, {
247
493
  { id: 'logs', text: 'View logs' }
248
494
  ]
249
495
  },
250
- footer: { text: 'Example menu' }
496
+ footer: {
497
+ text: 'Open documentation',
498
+ url: 'https://example.com/docs'
499
+ }
500
+ })
501
+ ```
502
+
503
+ Cards and carousel layout are supported:
504
+
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
+ }
251
522
  })
252
523
  ```
253
524
 
254
- Always validate button IDs on the server. Never treat a user-controlled button ID as an authorization grant.
525
+ Validate button IDs on the server. A button ID is an event value, not an authorization grant.
255
526
 
256
- ## Carousels and rich button grids
527
+ ### Reels and rich media
257
528
 
258
- 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`.
529
+ `sendReels` accepts an array of reel records. The main rich-media path is used for reels, sources, links, tables, and future rich payloads.
259
530
 
260
531
  ```js
261
- await sock.sendRichButtonGrid(jid, {
262
- text: 'Choose an option',
263
- footer: 'Select one',
264
- cards: [
265
- {
266
- image: { url: './first.jpg' },
267
- title: 'First card',
268
- caption: 'First card description',
269
- buttons: [
270
- { id: 'first', text: 'Select first' }
271
- ]
272
- },
273
- {
274
- image: { url: './second.jpg' },
275
- title: 'Second card',
276
- caption: 'Second card description',
277
- buttons: [
278
- { id: 'second', text: 'Select second' }
279
- ]
280
- }
281
- ]
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'
282
547
  })
283
548
  ```
284
549
 
285
- 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.
286
551
 
287
- For a single native-flow message:
552
+ For a general rich response:
288
553
 
289
554
  ```js
290
- await sock.sendMessage(jid, {
291
- text: 'Open the site',
292
- nativeFlow: [
293
- { 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
+ }
294
562
  ]
295
563
  })
296
564
  ```
297
565
 
298
- ## Media albums
566
+ ### Rich button grid
299
567
 
300
- 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.
301
569
 
302
570
  ```js
303
- await sock.sendMessage(jid, {
304
- album: [
571
+ await sock.sendRichButtonGrid(jid, {
572
+ text: 'Choose a service',
573
+ footer: 'Select one',
574
+ cards: [
305
575
  {
306
- image: { url: './image-1.jpg' },
307
- caption: 'First image'
576
+ title: 'Status',
577
+ image: { url: './status.jpg' },
578
+ buttons: [{ text: 'View status', id: 'status' }]
308
579
  },
309
580
  {
310
- image: { url: './image-2.jpg' },
311
- caption: 'Second image'
581
+ title: 'Logs',
582
+ image: { url: './logs.jpg' },
583
+ buttons: [{ text: 'View logs', id: 'logs' }]
312
584
  }
313
585
  ]
314
586
  })
315
587
  ```
316
588
 
317
- 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.
318
-
319
- ## Experimental bot metadata
320
-
321
- 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.
589
+ ## HTML screens and built-in apps
322
590
 
323
- ### Diagnostics metadata
591
+ ### Ordinary HTML message
324
592
 
325
593
  ```js
326
- await sock.sendBotDiagnostics(jid, {
327
- text: 'Diagnostics test',
328
- botBackend: 1, // 0 = AAPI, 1 = CLIPPY
329
- toolsUsed: ['web_search', 'image_search'],
330
- isThinking: true
594
+ await sock.sendHtmlMessage(jid, {
595
+ html: '<h1>Hello</h1><p>Rendered HTML content.</p>',
596
+ trustedSources: []
331
597
  })
332
598
  ```
333
599
 
334
- ### Command metadata
600
+ ### Tabbed rich HTML screen
335
601
 
336
602
  ```js
337
- await sock.sendBotCommand(jid, {
338
- text: 'Command metadata test',
339
- commandName: 'summarize',
340
- commandDescription: 'Summarize the current conversation',
341
- 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
+ ]
342
617
  })
343
618
  ```
344
619
 
345
- ### Resolved tool-call metadata
346
-
347
- ```js
348
- await sock.sendBotToolResult(jid, {
349
- text: 'Tool result test',
350
- toolCallId: `tool-${Date.now()}`,
351
- resolutionData: {
352
- status: 'success',
353
- result: { value: 'Completed' }
354
- }
355
- })
356
- ```
620
+ `trustedSources` and `url` are application-specific. Sanitize user-controlled HTML before sending it.
357
621
 
358
- You may provide a pre-serialized result instead:
622
+ ### Built-in screens
359
623
 
360
624
  ```js
361
- await sock.sendBotToolResult(jid, {
362
- text: 'Serialized result',
363
- toolCallId: 'tool-123',
364
- resolutionDataSerialized: JSON.stringify({
365
- status: 'success',
366
- value: 'Completed'
367
- })
368
- })
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')
369
629
  ```
370
630
 
371
- 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.
372
632
 
373
633
  ## Status updates
374
634
 
375
- Use `backgroundColor` rather than `backgroundArgb` in the high-level API:
635
+ `sendStatus` sends a status update and resolves recipients from `statusJidList`.
376
636
 
377
637
  ```js
378
638
  await sock.sendStatus({
379
- text: 'Service status: online',
639
+ text: 'Service online',
380
640
  backgroundColor: 0xff102a43,
381
641
  font: 2
382
642
  }, {
@@ -384,182 +644,156 @@ await sock.sendStatus({
384
644
  })
385
645
  ```
386
646
 
387
- 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
+ ```
388
655
 
389
- ## Groups
656
+ ## Groups and group history
390
657
 
391
658
  ```js
392
659
  const metadata = await sock.groupMetadata(groupJid)
660
+
393
661
  await sock.groupParticipantsUpdate(groupJid, [userJid], 'add')
394
662
  await sock.groupParticipantsUpdate(groupJid, [userJid], 'remove')
395
663
  await sock.groupSettingUpdate(groupJid, 'announcement')
396
664
  ```
397
665
 
398
- Group administration requires the corresponding privileges. Handle permission failures and disconnected sessions.
399
-
400
- ### Group member labels
666
+ ### Member labels
401
667
 
402
- `updateMemberLabel` sets the connected account's own custom member label in a
403
- group. The first argument must be the **group JID** (`123456789-123456@g.us`),
404
- not the member's individual JID:
668
+ `updateMemberLabel` sets the connected account's own label in a group.
405
669
 
406
670
  ```js
407
671
  await sock.updateMemberLabel(groupJid, 'Developer')
672
+ await sock.updateMemberLabel(groupJid, '') // remove the label
408
673
  ```
409
674
 
410
- To remove the label, pass an empty string:
675
+ ### Group history sharing
411
676
 
412
677
  ```js
413
- await sock.updateMemberLabel(groupJid, '')
678
+ await sock.toggleGroupHistory(groupJid, true) // all members
679
+ await sock.toggleGroupHistory(groupJid, false) // admins only
414
680
  ```
415
681
 
416
- Labels are limited to 30 characters by the protocol. This is not an admin
417
- operation for assigning labels to arbitrary participants; WhatsApp associates
418
- the label with the account that sends the protocol message.
682
+ ## Reports and moderation
419
683
 
420
- ## WhatsApp Flows and web views
684
+ The reporting APIs have different wire operations. Use the method that matches the action you intend to perform.
421
685
 
422
686
  ```js
423
- await sock.sendWhatsAppFlow(jid, {
424
- flowId: '1234567890',
425
- flowToken: 'example-token',
426
- cta: 'Open flow',
427
- text: 'Complete this form'
428
- })
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
+ )
429
693
 
430
- await sock.sendRichWebview(jid, {
431
- url: 'https://example.com/app',
432
- text: 'Open the app',
433
- buttonText: 'Open'
434
- })
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')
435
702
  ```
436
703
 
437
- 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.
438
705
 
439
- ## Message options
706
+ ## Experimental bot metadata
707
+
708
+ These methods attach experimental fields under `messageContextInfo.botMetadata`.
440
709
 
441
710
  ```js
442
- await sock.sendMessage(jid, {
443
- text: 'Disappearing message',
444
- ephemeralExpiration: 86400
711
+ await sock.sendBotDiagnostics(jid, {
712
+ text: 'Diagnostics test',
713
+ botBackend: 1,
714
+ toolsUsed: ['web_search'],
715
+ isThinking: false
445
716
  })
446
717
 
447
- await sock.sendMessage(jid, {
448
- text: 'Updated text',
449
- 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'
450
723
  })
451
724
 
452
- await sock.sendMessage(jid, {
453
- delete: message.key
725
+ await sock.sendBotToolResult(jid, {
726
+ text: 'Tool completed',
727
+ toolCallId: 'tool-123',
728
+ resolutionData: { status: 'success', value: 42 }
454
729
  })
455
730
  ```
456
731
 
457
- Raw protocol fields are version-dependent. Unsupported fields may be ignored or rejected by WhatsApp.
458
-
459
- ## 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.
460
733
 
461
- 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
462
735
 
463
736
  ```js
464
- sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
465
- if (connection !== 'close') return
466
-
467
- const status = new Boom(lastDisconnect?.error)?.output?.statusCode
468
- if (status === DisconnectReason.loggedOut) {
469
- console.error('Logged out; remove the old session and pair again.')
470
- return
471
- }
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
+ })
472
743
 
473
- 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'
474
748
  })
475
749
  ```
476
750
 
477
- ## Reporting, group history, rich HTML, and source grep
478
-
479
- 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.
480
752
 
481
- | API | Wire operation | Use |
482
- | --- | --- | --- |
483
- | `reportSpam(jid, messages, spamFlow, subject)` | `xmlns="spam"` → `spam_list` | Report a chat or group with message evidence. |
484
- | `reportUser(jid, reason, waitForAckMs)` | `xmlns="w:report"` → `user` | Report one user. |
485
- | `reportMessage(jid, messageId, reason, participantJid, waitForAckMs)` | `xmlns="w:report"` → `message` | Report one message. |
486
- | `reportAndBlockUser(jid, reason, waitForAckMs)` | `w:report` followed by block | Report a user and then block them. |
753
+ ## View-once utilities
487
754
 
488
755
  ```js
489
- // Existing plogme spam-list report with evidence.
490
- await sock.reportSpam(
491
- '1203630@g.us',
492
- [{ id: 'ABCD1234', from: '15550001111@s.whatsapp.net', t: 1710000000 }],
493
- 'group_info_report',
494
- 'Example group'
495
- )
756
+ const info = sock.getViewOnceInfo(message)
757
+ console.log(info)
496
758
 
497
- // New direct report methods.
498
- const acknowledged = await sock.reportUser('15550001111@s.whatsapp.net', 'spam')
499
- const messageAcknowledged = await sock.reportMessage(
500
- '15550001111@s.whatsapp.net', 'ABCD1234', 'harassment',
501
- '15550001111@s.whatsapp.net'
502
- )
503
- await sock.reportAndBlockUser('15550001111@s.whatsapp.net', 'scam')
759
+ if (sock.isViewOnce(message)) {
760
+ await sock.autoVV(message)
761
+ }
504
762
  ```
505
763
 
506
- `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.
507
765
 
508
- ### Group history sharing
766
+ ## Connection handling
509
767
 
510
- `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.
511
769
 
512
770
  ```js
513
- await sock.toggleGroupHistory('1203630@g.us', true)
514
- await sock.toggleGroupHistory('1203630@g.us', false)
515
- ```
516
-
517
- ### Two HTML transports
771
+ sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
772
+ if (connection !== 'close') return
518
773
 
519
- `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.
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
+ }
520
779
 
521
- ```js
522
- await sock.sendHtmlMessage(jid, {
523
- html: '<h1>Simple screen</h1><p>One HTML payload.</p>',
524
- trustedSources: []
780
+ setTimeout(() => {
781
+ connect().catch(console.error)
782
+ }, 3000)
525
783
  })
526
-
527
- await sock.sendRichHtmlMessage(jid, {
528
- title: 'Games',
529
- url: 'https://your-domain.example',
530
- trustedSources: ['your-domain.example'],
531
- tabs: [
532
- { tab_header: 'mini-app', html: '<canvas id="game"></canvas><script>/* game */</script>' },
533
- { tab_header: 'Instructions', html: '<h2>How to play</h2><p>Tap to jump.</p>' }
534
- ]
535
- })
536
- ```
537
-
538
- 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.
539
-
540
- 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:
541
-
542
- ```js
543
- await sock.sendMiniApp(jid)
544
- await sock.sendMiniApp(jid, { title: 'My mini-app' })
545
784
  ```
546
785
 
547
- 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.
548
-
549
- ### Reusable source grep
550
-
551
- `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.
786
+ ## Security and removed automatic behavior
552
787
 
553
- ```js
554
- const matches = sock.grepCode('sendRichHtmlMessage', { maxResults: 20 })
555
- console.log(matches)
556
- // [{ file: 'lib/Socket/messages-send.js', line: 2705, column: 12, text: '...' }]
788
+ Plogme does not automatically follow WhatsApp channels, fingerprint the host machine, contact a remote attestation service, or enforce rebranding through runtime code.
557
789
 
558
- const reportMatches = sock.grepCode('reportMessage', { caseSensitive: false })
559
- const regexMatches = sock.grepCode('report(User|Message)', { regex: true, flags: 'i' })
560
- ```
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)
561
795
 
562
- By default the search covers `lib` and `WAProto`; `includeReadme: true` also searches the README.
796
+ The project license still applies. Removing runtime enforcement does not change the license terms.
563
797
 
564
798
  ## Development
565
799
 
@@ -568,80 +802,18 @@ npm install
568
802
  npm test
569
803
  ```
570
804
 
571
- Before publishing, verify generated protobuf files, package contents, version metadata, and that no authentication state or environment files are included.
572
-
573
- ## License
574
-
575
- Review the included license before distributing applications built with `plogme`. Keep required notices with redistributed source and binaries.
576
-
577
- ## Support
578
-
579
- 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.
580
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.
581
808
 
582
- ## Ported WhatsApp Web protocol features
583
-
584
- 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.
585
-
586
- ### Animated wallpaper
587
-
588
- ```js
589
- await sock.sendMessage(jid, {
590
- chatTheme: {
591
- animatedWallpaper: { id: 'wallpaper-id', dimLevel: 0.25 }
592
- }
593
- })
594
- ```
595
-
596
- ### Sticker with audio
809
+ ## License and support
597
810
 
598
- ```js
599
- await sock.sendMessage(jid, {
600
- sticker: { url: './sticker.webp' },
601
- stickerAudio: { url: './sound.ogg' }
602
- })
603
- ```
811
+ Review [`LICENSE`](LICENSE) before redistributing applications or modified source.
604
812
 
605
- ### 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.
606
814
 
607
- ```js
608
- await sock.sendMessage(jid, {
609
- newsletterFollowerInvite: {
610
- newsletterJid: '123456789@newsletter',
611
- newsletterName: 'My newsletter',
612
- caption: 'Follow this channel'
613
- }
614
- })
615
-
616
- await sock.sendMessage(jid, {
617
- newsletterFollowerInviteMessageV2: {
618
- newsletterJid: '123456789@newsletter',
619
- newsletterName: 'My newsletter',
620
- caption: 'Follow this channel'
621
- }
622
- })
623
- ```
624
-
625
- ### Device-scoped relay options
626
-
627
- `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`.
628
-
629
- ```js
630
- await sock.relayMessage(jid, message, {
631
- messageId: messageId,
632
- isSecret: true
633
- })
634
- ```
635
-
636
- ### Explicit album wrapper
637
-
638
- 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:
639
-
640
- ```js
641
- await sock.sendAlbumMessage(jid, [
642
- { type: 'image', data: { url: './one.jpg' } },
643
- { type: 'image', data: { url: './two.jpg' } }
644
- ], { caption: 'Album caption' })
645
- ```
815
+ ## References
646
816
 
647
- 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"