import { Logger, Author, Adapter, ChatInstance, WebhookOptions, Message, AdapterPostableMessage, RawMessage, EmojiValue, FetchOptions, FetchResult, ThreadInfo, ChannelInfo, ListThreadsOptions, ListThreadsResult, FormattedContent, BaseFormatConverter, Root } from 'chat'; import { AuthenticationState, WAVersion, WAMessage } from 'baileys'; /** Decoded thread ID components for WhatsApp (Baileys) */ interface BaileysThreadId { /** WhatsApp JID, e.g. "15551234567@s.whatsapp.net" or "123456789@g.us" */ jid: string; } /** A participant returned by {@link BaileysAdapter.fetchGroupParticipants} */ interface BaileysGroupParticipant { /** The participant's JID, e.g. `"15551234567@s.whatsapp.net"` */ userId: string; /** True for both admin and super-admin roles */ isAdmin: boolean; /** True only for the group creator (super-admin) */ isSuperAdmin: boolean; } /** Named arguments for {@link BaileysAdapter.markRead}. */ interface BaileysMarkReadArgs { /** Encoded thread ID whose messages should be marked as read. */ threadId: string; /** Message IDs to acknowledge. */ messageIds: string[]; /** Optional sender JID/LID for group messages. */ participant?: string; } /** Named arguments for {@link BaileysAdapter.sendLocation}. */ interface BaileysSendLocationArgs { /** Encoded thread ID that should receive the location pin. */ threadId: string; /** Latitude in decimal degrees (WGS 84). */ latitude: number; /** Longitude in decimal degrees (WGS 84). */ longitude: number; /** Optional place name shown on the pin. */ name?: string; /** Optional address shown below the place name. */ address?: string; } /** * Decrypted poll vote delivered to {@link BaileysAdapterConfig.onPollVote}. * * WhatsApp poll votes are end-to-end encrypted; the adapter tracks each poll * sent via {@link BaileysAdapter.sendPoll} (using the Chat SDK's `StateAdapter` * for persistence) and decrypts incoming `pollUpdateMessage` events back into * the option names the voter selected. * * `selectedOptions` is empty when the voter cleared their vote. */ interface BaileysPollVote { /** Encoded thread ID where the poll lives */ threadId: string; /** Message ID of the original poll the bot sent */ pollMessageId: string; /** Original poll question */ question: string; /** Original poll options (in send order) */ options: string[]; /** Option names the voter currently has selected (empty = vote cleared) */ selectedOptions: string[]; /** Author info for the voter */ voter: Author; /** Raw Baileys vote message */ raw: WAMessage; /** * Arbitrary metadata supplied to {@link BaileysAdapter.sendPoll}. Persisted * alongside the poll's decryption state and round-tripped unchanged to every * vote on that poll. `undefined` when no metadata was provided. */ metadata?: unknown; } /** Configuration for the Baileys adapter */ interface BaileysAdapterConfig { /** * Adapter identity used by Chat SDK serialization and thread ID prefixes. * * For multi-account deployments in a single `Chat` instance, set a unique * value per account (for example: `"baileys-main"` and `"baileys-sales"`). * * Must not contain `:` because thread IDs use `name:encodedJid` format. * Defaults to `"baileys"`. */ adapterName?: string; /** * Baileys authentication state. * Obtain this via `useMultiFileAuthState` or a custom auth store * — typically in a separate setup script. * * @example * ```typescript * import { useMultiFileAuthState } from "baileys"; * const { state, saveCreds } = await useMultiFileAuthState("./auth_info"); * const adapter = createBaileysAdapter({ auth: { state, saveCreds } }); * ``` */ auth: { state: AuthenticationState; saveCreds: () => Promise; }; /** * WhatsApp Web version to use. * Auto-fetched via `fetchLatestBaileysVersion()` if not provided. */ version?: WAVersion; /** Bot display name (defaults to "baileys-bot") */ userName?: string; /** Logger instance */ logger?: Logger; /** * Called when a QR code string is emitted during initial connection. * Convert it to an image or terminal output with a library like `qrcode`. * * @example * ```typescript * import QRCode from "qrcode"; * onQR: async (qr) => console.log(await QRCode.toString(qr, { type: "terminal" })) * ``` */ onQR?: (qr: string) => void | Promise; /** * Phone number for pairing-code login (alternative to QR scanning). * Must be in E.164 format **without** the leading `+`. * * Example: `+1 (234) 567-8901` → `"12345678901"` * * When set, the adapter requests a pairing code shortly after a fresh, * unregistered socket begins connecting, then invokes `onPairingCode` with * the result. */ phoneNumber?: string; /** * Called with the 8-character pairing code when `phoneNumber` is set. * Display or forward this code so the user can enter it in WhatsApp. * * @example * ```typescript * onPairingCode: (code) => console.log("Enter this code in WhatsApp:", code) * ``` */ onPairingCode?: (code: string) => void; /** * Additional Baileys socket options passed directly to `makeWASocket`. * `auth` and `version` are managed by the adapter. */ socketOptions?: Record; /** * TTL (milliseconds) for stored poll metadata in the SDK's `StateAdapter`. * Defaults to 30 days. Pass `0` to keep entries until explicitly evicted. */ pollTtlMs?: number; } /** Handler signature for poll vote subscriptions. */ type BaileysPollVoteHandler = (vote: BaileysPollVote) => void | Promise; /** * A poll the adapter is currently tracking — i.e. one previously sent via * {@link BaileysAdapter.sendPoll} whose decryption metadata is still stored * in the SDK's `StateAdapter`. * * Use {@link BaileysAdapter.listTrackedPolls} on startup to re-register * per-poll handlers (`onPollVote(pollId, handler)`) after a process restart. */ interface BaileysTrackedPoll { /** Message ID of the original poll. Pass to `onPollVote(pollId, handler)`. */ pollMessageId: string; /** Encoded thread ID where the poll lives. */ threadId: string; /** Original poll question. */ question: string; /** Original poll options (in send order). */ options: string[]; /** Arbitrary metadata supplied to {@link BaileysAdapter.sendPoll}. */ metadata?: unknown; } /** Options for {@link BaileysAdapter.sendPoll}. */ interface BaileysSendPollOptions { /** * Arbitrary, opaque metadata to associate with this poll. Persisted in the * SDK's `StateAdapter` alongside the poll's decryption state and round-tripped * unchanged to every {@link BaileysPollVote} on this poll. Useful for * app-specific context (e.g. "askedBy" user id, quiz id, correlation id). */ metadata?: unknown; } /** Named arguments for {@link BaileysAdapter.sendPoll}. */ interface BaileysSendPollArgs { /** Encoded thread ID that should receive the poll. */ threadId: string; /** Poll question text. */ question: string; /** Poll options in send order. */ options: string[]; /** * How many options a user can select. * Defaults to `1`. Use `0` for unlimited. */ selectableCount?: number; /** Opaque app-level metadata persisted alongside poll state. */ metadata?: unknown; } declare class BaileysAdapter implements Adapter { readonly name: string; readonly userName: string; private _socket; private _chat; private _logger; private _config; private _converter; private _isConnected; private _shouldReconnect; /** Guard so we only request a pairing code once per socket lifetime. */ private _pairingCodeRequested; private _pairingCodeRequestTimer; private _pollVoteSubscriptions; private _sentMessageIds; constructor(config: BaileysAdapterConfig); get botUserId(): string | undefined; initialize(chat: ChatInstance): Promise; /** * Connect to WhatsApp via a persistent WebSocket. * * Call this after registering all handlers on your `Chat` instance. * The adapter handles automatic reconnection on unexpected disconnects. * * @example * ```typescript * const bot = new Chat({ adapters: { whatsapp: adapter }, ... }); * bot.onNewMention(async (thread, msg) => { ... }); * await adapter.connect(); * ``` */ connect(): Promise; /** Disconnect from WhatsApp and clean up the socket. */ disconnect(): Promise; private _createSocket; encodeThreadId(data: BaileysThreadId): string; decodeThreadId(threadId: string): BaileysThreadId; channelIdFromThreadId(threadId: string): string; isDM(threadId: string): boolean; /** * Baileys uses a persistent WebSocket — not inbound HTTP webhooks. * This method always returns HTTP 501 Not Implemented. * * To receive messages, call `adapter.connect()` instead. */ handleWebhook(_request: Request, _options?: WebhookOptions): Promise; parseMessage(raw: WAMessage): Message; postMessage(threadId: string, message: AdapterPostableMessage): Promise>; private _sendPostable; editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise>; deleteMessage(threadId: string, messageId: string): Promise; addReaction(threadId: string, messageId: string, emoji: EmojiValue | string, participant?: string): Promise; removeReaction(threadId: string, messageId: string, emoji: EmojiValue | string, participant?: string): Promise; fetchMessages(_threadId: string, _options?: FetchOptions): Promise>; fetchThread(threadId: string): Promise; /** * Fetch channel metadata. * * In WhatsApp, a "channel" is just the JID — a group or DM conversation. * For groups, this fetches the group subject and participant count. */ fetchChannelInfo(channelId: string): Promise; /** * Fetch channel-level messages. * * WhatsApp has no REST history API — same limitation as `fetchMessages`. * Implement your own store by persisting messages from `messages.upsert`. */ fetchChannelMessages(_channelId: string, _options?: FetchOptions): Promise>; /** * List threads in a channel. * * WhatsApp has no sub-threads — each conversation (JID) is a single * flat message stream. Returns an empty result accordingly. */ listThreads(_channelId: string, _options?: ListThreadsOptions): Promise>; /** * Post a message to a channel. * * In WhatsApp there is no channel/thread distinction — a channel IS the * conversation, so this delegates directly to `postMessage`. */ postChannelMessage(channelId: string, message: AdapterPostableMessage): Promise>; openDM(userId: string): Promise; startTyping(threadId: string, _status?: string): Promise; renderFormatted(content: FormattedContent): string; private _requireSocket; private _schedulePairingCodeRequest; private _clearPairingCodeRequest; /** * Send a quoted reply to a message, producing WhatsApp's native reply bubble. * * The Chat SDK's `thread.post()` has no concept of quoting a specific message. * Use this method directly on the adapter when you need the visual reply reference. * * Accepts either a plain string or any `AdapterPostableMessage` shape, * including ones with `attachments` / `files`. When attachments are present, * only the first outgoing message carries the quoted reference — matching * WhatsApp's native behaviour. * * @example * ```typescript * // text-only reply * await whatsapp.reply(message, "Got it!"); * * // reply with an image + caption, all quoting the original * await whatsapp.reply(message, { * raw: "Here's the screenshot you asked for", * files: [{ data: buffer, filename: "shot.png", mimeType: "image/png" }], * }); * ``` */ reply(message: Message, content: AdapterPostableMessage): Promise>; /** * Mark one or more messages as read, sending read receipts to the sender. * * The Chat SDK has no read-receipt concept — call this directly when you want * to explicitly acknowledge messages. * * @example * ```typescript * bot.onSubscribedMessage(async (thread, message) => { * await whatsapp.markRead({ * threadId: thread.id, * messageIds: [message.id], * participant: thread.isDM ? undefined : message.author.userId, * }); * }); * ``` */ markRead(args: BaileysMarkReadArgs): Promise; /** * @deprecated Use `markRead({ threadId, messageIds, participant })` instead. * Positional arguments will be removed in the next major version. */ markRead(threadId: string, messageIds: string[], participant?: string): Promise; /** * Set the bot's global WhatsApp presence — whether it appears online or offline. * * The Chat SDK's `thread.startTyping()` sends a per-chat composing presence. * This method controls the bot's top-level online/offline status. * * @example * ```typescript * // Call this after the WhatsApp connection is open. * await whatsapp.setPresence("available"); // appears online * await whatsapp.setPresence("unavailable"); // appears offline * ``` */ setPresence(presence: "available" | "unavailable"): Promise; /** * Send a location pin to a thread. * * WhatsApp supports native location messages (shown as a map pin). The Chat SDK * has no location type, so this is exposed as an adapter extension. * * @example * ```typescript * await whatsapp.sendLocation({ * threadId: thread.id, * latitude: 37.7749, * longitude: -122.4194, * name: "San Francisco", * address: "San Francisco, CA, USA", * }); * ``` */ sendLocation(args: BaileysSendLocationArgs): Promise>; /** * @deprecated Use `sendLocation({ threadId, latitude, longitude, name, address })` instead. * Positional arguments will be removed in the next major version. */ sendLocation(threadId: string, latitude: number, longitude: number, options?: { name?: string; address?: string; }): Promise>; /** * Send a WhatsApp poll to a thread. * * Polls are a native WhatsApp feature with no Chat SDK equivalent. * `selectableCount` controls how many options a user can pick (default: 1). * * @example * ```typescript * await whatsapp.sendPoll({ * threadId: thread.id, * question: "What time works for the call?", * options: ["10:00 AM", "2:00 PM", "5:00 PM"], * }); * * // With arbitrary metadata round-tripped to onPollVote: * await whatsapp.sendPoll({ * threadId: thread.id, * question: "Lunch?", * options: ["A", "B"], * metadata: { askedBy: userId }, * }); * ``` */ sendPoll(args: BaileysSendPollArgs): Promise>; /** * @deprecated Use `sendPoll({ threadId, question, options, selectableCount, metadata })` instead. * Positional arguments will be removed in the next major version. */ sendPoll(threadId: string, question: string, options: string[], selectableCount?: number, sendOptions?: BaileysSendPollOptions): Promise>; /** * Fetch the list of participants in a group thread. * * The Chat SDK has no group-membership concept. Use this to get the full * participant list including admin status. * * Throws if the thread is not a group. * * @example * ```typescript * const participants = await whatsapp.fetchGroupParticipants(thread.id); * const admins = participants.filter(p => p.isAdmin); * await thread.post(`Admins: ${admins.map(p => p.userId).join(", ")}`); * ``` */ fetchGroupParticipants(threadId: string): Promise; private _toRawMessage; private _sendOne; private _buildBaileysAuthor; private _trackSentMessage; private _trackSentMessageKey; private _isSentByAdapter; private _pruneSentMessageIds; /** * Register a handler for decrypted poll votes. * * WhatsApp poll votes arrive as `pollUpdateMessage` events and are E2E * encrypted. The adapter automatically decrypts votes for polls the bot * sent via {@link BaileysAdapter.sendPoll} (the poll's `messageSecret` is * persisted via the SDK's `StateAdapter`) and dispatches them here. * * Decrypted votes are also forwarded to `chat.processMessage` with the * selected option names joined as the message text — so handlers like * `chat.onSubscribedMessage` still see them. Use this method when you need * the structured payload (poll question, voter, selected option names). * * Mirrors the Chat SDK's filtered-handler shape (`onReaction(emoji, fn)`, * `onAction(actionIds, fn)`). * * @example * ```ts * // All votes on any poll the bot has sent. * wa.onPollVote((vote) => { * console.log(`${vote.voter.userName}: ${vote.selectedOptions.join(", ")}`); * }); * * // Votes scoped to a single poll. * const poll = await wa.sendPoll({ * threadId: thread.id, * question: "Lunch?", * options: ["A", "B"], * }); * wa.onPollVote(poll.id, async (vote) => { * await thread.post(`${vote.voter.userName} picked ${vote.selectedOptions[0]}`); * }); * * // Votes scoped to several polls. * wa.onPollVote([pollA.id, pollB.id], handler); * ``` * * Multiple handlers can be registered; all matching handlers run in * registration order. An empty `selectedOptions` array means the voter * cleared their vote. */ onPollVote(handler: BaileysPollVoteHandler): void; onPollVote(pollMessageIds: string | string[], handler: BaileysPollVoteHandler): void; /** * List polls the adapter is currently tracking — i.e. polls previously sent * via {@link BaileysAdapter.sendPoll} whose decryption metadata still lives * in the SDK's `StateAdapter`. * * Use this on startup to re-register per-poll handlers after a process * restart (in-memory `onPollVote(pollId, handler)` registrations don't * survive restarts, but the stored metadata does). * * Stale index entries (entries whose poll TTL has expired or were cleared * via {@link BaileysAdapter.forgetPoll}) are filtered out — the returned * list only contains polls that can actually decrypt incoming votes. * * @example * ```ts * const tracked = await wa.listTrackedPolls(); * for (const poll of tracked) { * wa.onPollVote(poll.pollMessageId, (vote) => { * console.log(`vote on "${poll.question}":`, vote.selectedOptions); * }); * } * ``` */ listTrackedPolls(): Promise; /** * Forget a tracked poll — deletes its decryption metadata from the * `StateAdapter`. Subsequent votes on this poll won't be decrypted. * * Useful once a poll has closed and you no longer want to receive votes * on it (e.g. the deadline passed). The index entry is left in place but * filtered out by {@link BaileysAdapter.listTrackedPolls}. */ forgetPoll(pollMessageId: string): Promise; private _pollKey; private _pollIndexKey; private _rememberPoll; private _handlePollUpdate; } /** * Format converter for WhatsApp (Baileys). * * WhatsApp uses its own lightweight markup: * - `*bold*` → strong * - `_italic_` → emphasis * - `~strikethrough~`→ delete * - `` `code` `` → inlineCode * - ```` ```block``` ```` → code block * * Standard Markdown links are not supported by WhatsApp — links become * `text (url)` in the platform output. */ declare class BaileysFormatConverter extends BaseFormatConverter { /** * Convert WhatsApp-formatted text to mdast AST. * Pre-processes WhatsApp-specific syntax to standard Markdown before parsing. */ toAst(platformText: string): Root; /** * Convert mdast AST to WhatsApp text format. */ fromAst(ast: Root): string; private convertNode; private convertChildren; } /** * Create a WhatsApp (Baileys) adapter for Chat SDK. * * Auth is the caller's responsibility — obtain it with `useMultiFileAuthState` * or any compatible Baileys auth store, typically in a separate setup script. * * @example * ```typescript * import { useMultiFileAuthState } from "baileys"; * import { createBaileysAdapter } from "chat-adapter-baileys"; * import { Chat } from "chat"; * * const { state, saveCreds } = await useMultiFileAuthState("./auth_info"); * * const adapter = createBaileysAdapter({ * auth: { state, saveCreds }, * }); * * const bot = new Chat({ * userName: "mybot", * adapters: { whatsapp: adapter }, * state: myStateAdapter, * }); * * bot.onNewMention(async (thread, message) => { * await thread.post(`Hello, ${message.author.fullName}!`); * }); * * await bot.initialize(); * await adapter.connect(); * ``` */ declare function createBaileysAdapter(config: BaileysAdapterConfig): BaileysAdapter; /** * Type guard for narrowing a Chat SDK adapter to {@link BaileysAdapter}. * * @example * ```typescript * const adapter = thread.adapter; * if (isBaileysAdapter(adapter)) { * await adapter.reply(message, "Got it!"); * } * ``` */ declare function isBaileysAdapter(adapter: unknown): adapter is BaileysAdapter; /** * Require that a Chat SDK context belongs to a {@link BaileysAdapter}. * * Accepts either an adapter directly or any object with an `adapter` property, * such as `Thread` and `Channel`. * * @example * ```typescript * const wa = requireBaileysAdapter(thread); * await wa.markRead({ * threadId: thread.id, * messageIds: [message.id], * }); * ``` */ declare function requireBaileysAdapter(value: unknown): BaileysAdapter; export { BaileysAdapter, type BaileysAdapterConfig, BaileysFormatConverter, type BaileysGroupParticipant, type BaileysMarkReadArgs, type BaileysPollVote, type BaileysPollVoteHandler, type BaileysSendLocationArgs, type BaileysSendPollArgs, type BaileysSendPollOptions, type BaileysThreadId, type BaileysTrackedPoll, createBaileysAdapter, isBaileysAdapter, requireBaileysAdapter };