@vanzxy/baileys 1.6.4 → 1.6.6

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.

Potentially problematic release.


This version of @vanzxy/baileys might be problematic. Click here for more details.

@@ -5,6 +5,11 @@ export const CALL_VIDEO_PREFIX: "https://call.whatsapp.com/video/";
5
5
  export const CALL_AUDIO_PREFIX: "https://call.whatsapp.com/voice/";
6
6
  export const DONATE_URL: "";
7
7
  export const LIBRARY_NAME: "vanzxy/baileys";
8
+ export const COMPANION_DEVICE_VERSION: {
9
+ primary: number;
10
+ secondary: number;
11
+ tertiary: number;
12
+ };
8
13
  export const DEF_CALLBACK_PREFIX: "CB:";
9
14
  export const DEF_TAG_PREFIX: "TAG:";
10
15
  export const PHONE_CONNECTION_CB: "CB:Pong";
@@ -2,10 +2,27 @@ import { proto } from '../../WAProto/index.js';
2
2
  import { makeLibSignalRepository } from '../Signal/libsignal.js';
3
3
  import { Browsers } from '../Utils/browser-utils.js';
4
4
  import logger from '../Utils/logger.js';
5
- // Vanz@Fix: dinaikin ke angka yang sama kayak baileys resmi (per 14/8/2026)
6
- // — ini cuma fallback kalau fetchLatestBaileysVersion() gagal total
7
- // (mis. GitHub gak bisa diakses), jadi tetap worth di-update berkala.
8
- const version = [2, 3000, 1045191189];
5
+ // Vanz@Fix: disamain sama baseline yang dipake pas diff WAProto (lihat
6
+ // WAProto/CHANGELOG-proto-upgrade.md sempet lebih baru dari fallback
7
+ // lama, jadi socket ngaku versi lebih tua dari field yang udah didukung
8
+ // proto-nya sendiri). Ini cuma fallback kalau fetchLatestBaileysVersion()
9
+ // gagal total (mis. GitHub gak bisa diakses) — tetap worth di-update
10
+ // berkala, dan runtime tetap disaranin pake fetchLatestWaWebVersion()
11
+ // dulu (parse sw.js langsung) baru fallback ke fetchLatestBaileysVersion()
12
+ // / konstanta ini, karena fetchLatestBaileysVersion resmi Baileys ada
13
+ // history bug isLatest:true padahal versinya stale (WhiskeySockets#2679).
14
+ const version = [2, 3000, 1046350168];
15
+ // Vanz@Fix (bug 65): this was an untracked, uncommented magic number inline
16
+ // inside generateRegistrationNode() in validate-connection.js — a *second*,
17
+ // independent hardcoded version (WA's "companion/device-props" version sent
18
+ // during device pairing, distinct from `version` above which is the WA Web
19
+ // client version used for appVersion/buildHash). It can go stale the exact
20
+ // same way `version` can (same failure mode as WhiskeySockets#2370/#2485),
21
+ // but unlike `version`, it isn't covered by fetchLatestWaWebVersion(),
22
+ // fetchLatestBaileysVersion(), or fetchBestWaVersion() — none of those touch
23
+ // it, so it was silently unmaintained. Named + documented here so it's at
24
+ // least visible and update-able; still worth bumping periodically by hand.
25
+ export const COMPANION_DEVICE_VERSION = { primary: 10, secondary: 15, tertiary: 7 };
9
26
  export const UNAUTHORIZED_CODES = [401, 403, 419];
10
27
  export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
11
28
  export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
package/lib/Utils/A2UI.js CHANGED
@@ -16,7 +16,7 @@
16
16
  "use strict";
17
17
 
18
18
  import crypto from "crypto";
19
- import { generateWAMessageFromContent } from "./messages.js";
19
+ import { generateWAMessageFromContent, prepareWAMessageMedia } from "./messages.js";
20
20
  import { getBizBinaryNode } from "../WABinary/index.js";
21
21
 
22
22
  class A2UI {
@@ -133,11 +133,45 @@ class A2UI {
133
133
  };
134
134
  return this;
135
135
  }
136
+ // Vanz@Add 30-08-26 --- send(): terminal method so A2UI can be used as a one-liner like
137
+ // the other builders (Button/ButtonV2/AIRich `.send()`), instead of always needing the
138
+ // separate `sendA2UIWidget()` call. Thin wrapper only — root([...ids])/listCard() must
139
+ // still be called first, same as before build(); this doesn't change the id-returning
140
+ // factory API (child methods still return ids, not `this`, since sibling nodes need to
141
+ // reference each other by id when wiring children/trigger/content).
142
+ async send(client, jid, opts = {}) {
143
+ return sendA2UIWidget(client, jid, { ...opts, a2ui: this });
144
+ }
145
+ // Vanz@Add 30-08-26 --- validates that every structural id-reference (child/children on
146
+ // Button/Card/Column/Row, trigger/content on Modal) points at a component that was
147
+ // actually registered. Without this, a typo'd id just gets written into the wire payload
148
+ // as-is and the failure only surfaces as a broken/blank widget on the WA client, with no
149
+ // error on this end. Only covers the dedicated methods' known reference keys — raw()'s
150
+ // free-form props aren't inspected, since there's no way to know which of those are id
151
+ // references vs plain data.
152
+ #validateRefs(components) {
153
+ const validIds = new Set(components.map((c) => c.id));
154
+ for (const c of components) {
155
+ for (const key of ["child", "trigger", "content"]) {
156
+ if (c[key] !== undefined && !validIds.has(c[key])) {
157
+ throw new Error(`A2UI: component "${c.id}" (${c.component}) references unknown id "${c[key]}" via "${key}"`);
158
+ }
159
+ }
160
+ if (Array.isArray(c.children)) {
161
+ for (const childId of c.children) {
162
+ if (!validIds.has(childId)) {
163
+ throw new Error(`A2UI: component "${c.id}" (${c.component}) references unknown id "${childId}" in "children"`);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
136
169
  build({ uuid = crypto.randomUUID(), surfaceId, type = "im_a2ui", wrapped = true } = {}) {
137
170
  if (this._listCardPayload) return this._listCardPayload;
138
171
  if (!this._rootChildren.length) throw new Error("Call root([...ids]) before build()");
139
172
  const root = { id: "root", component: "Column", children: this._rootChildren };
140
173
  const components = [root, ...this._components.values()];
174
+ this.#validateRefs(components);
141
175
  const data = wrapped
142
176
  ? {
143
177
  version: this._version,
@@ -171,7 +205,13 @@ async function sendA2UIWidget(client, jid, {
171
205
  quoted,
172
206
  type = "im_a2ui",
173
207
  wrapped = true,
174
- singleScreen = false
208
+ singleScreen = false,
209
+ // Vanz@Add 30-08-26 --- optional header media, mirroring Button/ButtonV2's toCard()
210
+ // pattern (prepareWAMessageMedia + client.waUploadToServer). { title, subtitle, image
211
+ // | video | document: path/buffer/{url} } — media is mutually exclusive, image wins if
212
+ // more than one is passed. Falls back to a title/subtitle-only header (no attachment)
213
+ // when no media is given, same shape as before this change.
214
+ header
175
215
  } = {}) {
176
216
  if (!client) throw new Error("Socket is required");
177
217
  if (!(a2ui instanceof A2UI)) throw new TypeError("a2ui must be an A2UI instance");
@@ -187,6 +227,26 @@ async function sendA2UIWidget(client, jid, {
187
227
  }
188
228
  : { messageParamsJson: "" };
189
229
 
230
+ const headerMediaData = header?.image
231
+ ? { image: header.image }
232
+ : header?.video
233
+ ? { video: header.video }
234
+ : header?.document
235
+ ? { document: header.document }
236
+ : null;
237
+
238
+ const headerBlock = {
239
+ ...(header?.title !== undefined ? { title: header.title } : {}),
240
+ ...(header?.subtitle !== undefined ? { subtitle: header.subtitle } : {}),
241
+ hasMediaAttachment: !!headerMediaData,
242
+ ...(headerMediaData
243
+ ? await prepareWAMessageMedia(headerMediaData, { upload: client.waUploadToServer }).catch((e) => {
244
+ if (String(e).includes("Invalid media type")) return headerMediaData;
245
+ throw e;
246
+ })
247
+ : {})
248
+ };
249
+
190
250
  const interactiveMessage = singleScreen
191
251
  ? {
192
252
  nativeFlowMessage,
@@ -194,7 +254,7 @@ async function sendA2UIWidget(client, jid, {
194
254
  ...(expiration || Object.keys(contextInfo).length ? { contextInfo: { ...(expiration ? { expiration } : {}), ...contextInfo } } : {})
195
255
  }
196
256
  : {
197
- header: { hasMediaAttachment: false },
257
+ header: headerBlock,
198
258
  body: { text: bodyText },
199
259
  ...(footer ? { footer: { text: footer } } : {}),
200
260
  nativeFlowMessage,
@@ -50,6 +50,9 @@ export declare const createAntiDeleteHandler: (store: MessageStore, onDelete?: (
50
50
  key: WAMessageKey;
51
51
  update: Partial<WAMessage>;
52
52
  }>) => DeletedMessageInfo[];
53
+ export declare const createAntiDeleteUpsertHandler: (store: MessageStore, onDelete?: (info: DeletedMessageInfo) => void) => ({ messages }: {
54
+ messages: WAMessage[];
55
+ }) => DeletedMessageInfo[];
53
56
  export declare const createMessageStoreHandler: (store: MessageStore) => ({ messages }: {
54
57
  messages: WAMessage[];
55
58
  }) => void;
@@ -61,6 +64,9 @@ declare const _default: {
61
64
  key: WAMessageKey;
62
65
  update: Partial<WAMessage>;
63
66
  }>) => DeletedMessageInfo[];
67
+ createAntiDeleteUpsertHandler: (store: MessageStore, onDelete?: (info: DeletedMessageInfo) => void) => ({ messages }: {
68
+ messages: WAMessage[];
69
+ }) => DeletedMessageInfo[];
64
70
  createMessageStoreHandler: (store: MessageStore) => ({ messages }: {
65
71
  messages: WAMessage[];
66
72
  }) => void;
@@ -156,6 +156,34 @@ export const createAntiDeleteHandler = (store, onDelete) => {
156
156
  return deletedMessages;
157
157
  };
158
158
  };
159
+ /**
160
+ * Vanz@Fix (bug 58): `createAntiDeleteHandler` above only listens on
161
+ * `messages.update` with `messageStubType === REVOKE`. In practice, most
162
+ * delete-for-everyone events actually arrive as a *new* message on
163
+ * `messages.upsert` carrying a `protocolMessage` of type REVOKE (the
164
+ * `isDeleteMessage`/`getDeletedMessageKey` helpers above existed for this but
165
+ * were never wired to anything). Wire this into `sock.ev.on('messages.upsert', ...)`
166
+ * alongside `createMessageStoreHandler` to actually catch those.
167
+ */
168
+ export const createAntiDeleteUpsertHandler = (store, onDelete) => {
169
+ return ({ messages }) => {
170
+ const deletedMessages = [];
171
+ for (const message of messages) {
172
+ if (!isDeleteMessage(message))
173
+ continue;
174
+ const key = getDeletedMessageKey(message);
175
+ if (!key)
176
+ continue;
177
+ const deletedBy = message.key.participant || message.key.remoteJid;
178
+ const info = store.markAsDeleted(key, deletedBy);
179
+ if (info) {
180
+ deletedMessages.push(info);
181
+ onDelete?.(info);
182
+ }
183
+ }
184
+ return deletedMessages;
185
+ };
186
+ };
159
187
  /**
160
188
  * Wire this into `sock.ev.on('messages.upsert', ...)` to keep `store` populated.
161
189
  * Skips protocol/sender-key-distribution messages, which carry no user content.
@@ -180,6 +208,7 @@ export default {
180
208
  isDeleteMessage,
181
209
  getDeletedMessageKey,
182
210
  createAntiDeleteHandler,
211
+ createAntiDeleteUpsertHandler,
183
212
  createMessageStoreHandler
184
213
  };
185
214
  //# sourceMappingURL=anti-delete.js.map
@@ -195,15 +195,20 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
195
195
  if (!ctx) {
196
196
  // No transaction - direct write with queue protection
197
197
  const types = Object.keys(data);
198
- // Process pre-keys with validation
199
- for (const type_ of types) {
200
- const type = type_;
198
+ // Vanz@Fix (bug 66): validateDeletions() used to run *before* this,
199
+ // serialized only against PreKeyManager's own internal queue —
200
+ // a separate PQueue instance from getQueue(type) below, both keyed
201
+ // 'pre-key' but never synchronized with each other. Two concurrent
202
+ // set() calls for pre-key data could validate against a store state
203
+ // that a still-pending write from the other call hadn't applied yet,
204
+ // letting a stale deletion go through or a just-deleted key get
205
+ // resurrected by data racing back in. Validation now happens inside
206
+ // the same per-type queue job as the write, so it's ordered against
207
+ // any other set() for that type instead of racing it.
208
+ await Promise.all(types.map(type => getQueue(type).add(async () => {
201
209
  if (type === 'pre-key') {
202
210
  await preKeyManager.validateDeletions(data, type);
203
211
  }
204
- }
205
- // Write all data in parallel
206
- await Promise.all(types.map(type => getQueue(type).add(async () => {
207
212
  const typeData = { [type]: data[type] };
208
213
  await state.set(typeData);
209
214
  })));
@@ -63,7 +63,6 @@ export class AutoReplyHandler {
63
63
  }
64
64
  setCooldown(ruleId, jid, cooldown) {
65
65
  this.cooldowns.set(`${ruleId}:${jid}`, Date.now() + cooldown);
66
- this.globalCooldown.set(jid, Date.now());
67
66
  }
68
67
  matchRule(text, rule) {
69
68
  if (!rule.active)
@@ -137,6 +136,10 @@ export class AutoReplyHandler {
137
136
  await this.sendPresence(jid, 'paused');
138
137
  }
139
138
  await this.sendMessage(jid, response, rule.quoted ? { quoted: message } : undefined);
139
+ // Vanz@Fix (bug 60): global cooldown used to only get stamped inside
140
+ // setCooldown(), which only ran when the matched rule had its own
141
+ // cooldown — so rules without one never fed the global anti-spam gate.
142
+ this.globalCooldown.set(jid, Date.now());
140
143
  if (rule.cooldown)
141
144
  this.setCooldown(rule.id, jid, rule.cooldown);
142
145
  this.options.onReply(rule, message, response);
@@ -0,0 +1,8 @@
1
+ import type { BaileysEventEmitter } from '../Types/index.js';
2
+ /** Monkey-patches ev.emit to append every event as an NDJSON line to `filename`. */
3
+ export declare const captureEventStream: (ev: BaileysEventEmitter, filename: string) => void;
4
+ /** Reads an NDJSON file written by captureEventStream and replays each event on a new EventEmitter. */
5
+ export declare const readAndEmitEventStream: (filename: string, delayIntervalMs?: number) => {
6
+ ev: BaileysEventEmitter;
7
+ task: Promise<void>;
8
+ };
@@ -0,0 +1,62 @@
1
+ // Ported from @queenanya/baileys `addons/baileys-event-stream.ts`.
2
+ // Adjustments for @vanzxy/baileys: import paths rewired to this fork's own
3
+ // `generics.js` (delay) and `make-mutex.js` (makeMutex) — both already
4
+ // present, no logic changes otherwise.
5
+ import EventEmitter from 'events';
6
+ import { createReadStream } from 'fs';
7
+ import { writeFile } from 'fs/promises';
8
+ import { createInterface } from 'readline';
9
+ import { delay } from './generics.js';
10
+ import { makeMutex } from './make-mutex.js';
11
+
12
+ /**
13
+ * Monkey-patches `ev.emit` to append every Baileys event as a JSON line
14
+ * (NDJSON) to `filename`. Useful for debugging or replaying sessions.
15
+ *
16
+ * @example
17
+ * captureEventStream(sock.ev, './events.ndjson')
18
+ */
19
+ export const captureEventStream = (ev, filename) => {
20
+ const originalEmit = ev.emit.bind(ev);
21
+ const writeMutex = makeMutex();
22
+ const patchedEmit = (event, ...rest) => {
23
+ const line = JSON.stringify({ timestamp: Date.now(), event, data: rest[0] }) + '\n';
24
+ const result = originalEmit(event, ...rest);
25
+ void writeMutex.mutex(async () => {
26
+ await writeFile(filename, line, { flag: 'a' });
27
+ });
28
+ return result;
29
+ };
30
+ ev.emit = patchedEmit;
31
+ };
32
+
33
+ /**
34
+ * Reads an NDJSON file written by {@link captureEventStream} and replays
35
+ * each event on a new EventEmitter.
36
+ *
37
+ * @param filename Path to the NDJSON file.
38
+ * @param delayIntervalMs Milliseconds to wait between events (default 0).
39
+ * @returns `{ ev, task }` — ev is the emitter, task resolves when done.
40
+ */
41
+ export const readAndEmitEventStream = (filename, delayIntervalMs = 0) => {
42
+ const ev = new EventEmitter();
43
+ const fireEvents = async () => {
44
+ const fileStream = createReadStream(filename);
45
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
46
+ for await (const line of rl) {
47
+ if (!line.trim())
48
+ continue;
49
+ try {
50
+ const { event, data } = JSON.parse(line);
51
+ ev.emit(event, data);
52
+ if (delayIntervalMs)
53
+ await delay(delayIntervalMs);
54
+ }
55
+ catch {
56
+ // skip malformed lines
57
+ }
58
+ }
59
+ fileStream.destroy();
60
+ };
61
+ return { ev, task: fireEvents() };
62
+ };
@@ -200,10 +200,18 @@ export const uploadingNecessaryImages = async (images, waUploadToServer, timeout
200
200
  const hasher = createHash('sha256');
201
201
  const filePath = join(tmpdir(), 'img' + generateMessageIDV2());
202
202
  const encFileWriteStream = createWriteStream(filePath);
203
+ // Vanz@Fix (bug 61): no 'error' listener meant a write failure (e.g. disk
204
+ // full) would surface as an unhandled 'error' event instead of rejecting
205
+ // this promise; and the stream was never closed before unlink() below.
206
+ const writeStreamError = new Promise((_, reject) => encFileWriteStream.on('error', reject));
203
207
  for await (const block of stream) {
204
208
  hasher.update(block);
205
209
  encFileWriteStream.write(block);
206
210
  }
211
+ await Promise.race([
212
+ new Promise((resolve) => encFileWriteStream.end(resolve)),
213
+ writeStreamError
214
+ ]);
207
215
  const sha = hasher.digest('base64');
208
216
  const { directPath } = await waUploadToServer(filePath, {
209
217
  mediaType: 'product-catalog-image',
@@ -491,8 +491,15 @@ export function convertToInteractiveMessage(content) {
491
491
  }
492
492
  };
493
493
 
494
+ // Vanz@Fix 30-08-26 --- subtitle was being used only as a fallback for a missing
495
+ // title, then discarded — if BOTH title and subtitle were provided, subtitle never
496
+ // made it into the header at all (silently dropped, despite header.subtitle being a
497
+ // real proto field per MessageBuilder.js's toCard()). Write both independently.
494
498
  if (content.title || content.subtitle) {
495
- interactiveMessage.header = { title: content.title ?? content.subtitle ?? '' };
499
+ interactiveMessage.header = {
500
+ title: content.title ?? content.subtitle ?? '',
501
+ ...(content.title && content.subtitle ? { subtitle: content.subtitle } : {})
502
+ };
496
503
  }
497
504
 
498
505
  if (content.text) {
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Chat Control Utilities
3
+ *
4
+ * Source: @innovatorssoft/baileys (chat-control.js)
5
+ * Rewritten as clean TypeScript with full types and JSDoc.
6
+ *
7
+ * Three components:
8
+ * - TypingIndicator — composing / recording presence helpers
9
+ * - PinnedMessagesManager — client-side pin tracking
10
+ * - ReadReceiptController — configurable automatic read receipts
11
+ */
12
+ /**
13
+ * Standard disappearing-message duration constants (in seconds).
14
+ * Pass to `sock.sendMessage(jid, { disappearingMessagesInChat: DISAPPEARING_DURATIONS.DAYS_7 })`.
15
+ */
16
+ export declare const DISAPPEARING_DURATIONS: {
17
+ /** Disable disappearing messages */
18
+ readonly OFF: 0;
19
+ /** 24 hours */
20
+ readonly HOURS_24: 86400;
21
+ /** 7 days */
22
+ readonly DAYS_7: 604800;
23
+ /** 90 days */
24
+ readonly DAYS_90: 7776000;
25
+ };
26
+ export type DisappearingDuration = (typeof DISAPPEARING_DURATIONS)[keyof typeof DISAPPEARING_DURATIONS];
27
+ type PresenceType = 'composing' | 'recording' | 'paused' | 'available' | 'unavailable';
28
+ type SendPresence = (jid: string, presence: PresenceType) => Promise<void>;
29
+ type TypingOptions = {
30
+ /** Auto-stop after this many ms (default: no auto-stop) */
31
+ duration?: number;
32
+ /** Whether to auto-pause on timeout — default `true` */
33
+ autoPause?: boolean;
34
+ };
35
+ /**
36
+ * Manages composing ("typing...") and recording ("recording...") presence
37
+ * indicators with per-JID timer tracking.
38
+ *
39
+ * @example
40
+ * const typing = createTypingIndicator(
41
+ * (jid, presence) => sock.sendPresenceUpdate(presence, jid)
42
+ * )
43
+ *
44
+ * // Simulate typing then send a message
45
+ * const result = await typing.simulateTyping(jid, 1500, () =>
46
+ * sock.sendMessage(jid, { text: 'Hello!' })
47
+ * )
48
+ */
49
+ export declare class TypingIndicator {
50
+ private readonly sendPresence;
51
+ private readonly timers;
52
+ constructor(sendPresence: SendPresence);
53
+ /** Show the "typing..." (composing) indicator for a JID. */
54
+ startTyping(jid: string, options?: TypingOptions): Promise<void>;
55
+ /** Show the "recording..." (audio/video) indicator for a JID. */
56
+ startRecording(jid: string, options?: TypingOptions): Promise<void>;
57
+ /** Stop any active composing/recording indicator for a JID. */
58
+ stopTyping(jid: string): Promise<void>;
59
+ /** Stop all active indicators. */
60
+ stopAll(): Promise<void>;
61
+ /**
62
+ * Show typing for `durationMs`, run `callback`, then stop the indicator.
63
+ *
64
+ * @template T
65
+ * @returns The return value of `callback`
66
+ *
67
+ * @example
68
+ * await typing.simulateTyping(jid, 2000, async () => {
69
+ * await sock.sendMessage(jid, { text: 'Here is your answer' })
70
+ * })
71
+ */
72
+ simulateTyping<T>(jid: string, durationMs: number, callback: () => Promise<T> | T): Promise<T>;
73
+ private clearTimer;
74
+ }
75
+ /** Factory — create a TypingIndicator. */
76
+ export declare const createTypingIndicator: (sendPresence: SendPresence) => TypingIndicator;
77
+ export type PinnedMessage = {
78
+ messageId: string;
79
+ jid: string;
80
+ pinnedAt: Date;
81
+ pinnedBy?: string;
82
+ expiresAt?: Date;
83
+ };
84
+ /**
85
+ * Client-side tracker for pinned messages.
86
+ * Listen to `messages.update` for `pinInChatMessage` protocol messages and call
87
+ * `manager.pin(jid, msgId, pinnedBy)` / `manager.unpin(jid, msgId)` accordingly.
88
+ */
89
+ export declare class PinnedMessagesManager {
90
+ private readonly store;
91
+ /**
92
+ * Record a newly pinned message.
93
+ * @returns The created pin entry
94
+ */
95
+ pin(jid: string, messageId: string, pinnedBy?: string, expiresAt?: Date): PinnedMessage;
96
+ /**
97
+ * Remove a pinned message.
98
+ * @returns `true` if the pin was found and removed, `false` otherwise
99
+ */
100
+ unpin(jid: string, messageId: string): boolean;
101
+ /** Get all pinned messages for a chat. */
102
+ getPinned(jid: string): PinnedMessage[];
103
+ /** Check if a message is pinned in a chat. */
104
+ isPinned(jid: string, messageId: string): boolean;
105
+ /** Remove all pins for a chat. */
106
+ clearPins(jid: string): void;
107
+ /**
108
+ * Evict pins whose `expiresAt` is in the past.
109
+ * @returns Number of expired pins removed
110
+ */
111
+ clearExpired(): number;
112
+ /** Total pin count across all chats. */
113
+ get totalPins(): number;
114
+ }
115
+ /** Factory — create a PinnedMessagesManager. */
116
+ export declare const createPinnedMessagesManager: () => PinnedMessagesManager;
117
+ export type ReadReceiptConfig = {
118
+ /** Whether to send read receipts at all (default: `true`) */
119
+ enabled?: boolean;
120
+ /** JIDs to never send receipts for */
121
+ excludeJids?: string[];
122
+ /** Delay before marking as read in ms (default: `0`) */
123
+ readDelay?: number;
124
+ };
125
+ export type ReadReceiptController = {
126
+ setConfig(config: Partial<ReadReceiptConfig>): void;
127
+ getConfig(): Required<ReadReceiptConfig>;
128
+ enable(): void;
129
+ disable(): void;
130
+ isEnabled(): boolean;
131
+ /**
132
+ * Mark messages as read, respecting the current config.
133
+ * No-op if disabled or JID is excluded.
134
+ */
135
+ markRead(jid: string, participant: string | null | undefined, messageIds: string[]): Promise<void>;
136
+ /** Mark messages as read regardless of config. */
137
+ forceMarkRead(jid: string, participant: string | null | undefined, messageIds: string[]): Promise<void>;
138
+ };
139
+ type SendReadReceipt = (jid: string, participant: string | null | undefined, messageIds: string[]) => Promise<void>;
140
+ /**
141
+ * Create a read-receipt controller with optional auto-delay and per-JID exclusions.
142
+ *
143
+ * @example
144
+ * const readCtrl = createReadReceiptController(
145
+ * (jid, participant, ids) => sock.readMessages(ids.map(id => ({ remoteJid: jid, id, participant }))),
146
+ * { enabled: true, readDelay: 500, excludeJids: [spamJid] }
147
+ * )
148
+ *
149
+ * sock.ev.on('messages.upsert', ({ messages }) => {
150
+ * for (const msg of messages) {
151
+ * const { key } = msg
152
+ * if (!key.fromMe)
153
+ * readCtrl.markRead(key.remoteJid!, key.participant, [key.id!])
154
+ * }
155
+ * })
156
+ */
157
+ export declare const createReadReceiptController: (sendReadReceipt: SendReadReceipt, config?: ReadReceiptConfig) => ReadReceiptController;
158
+ export {};
159
+ //# sourceMappingURL=chat-control.d.ts.map