zaileys 4.8.8 → 4.9.0

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.
@@ -1,7 +1,7 @@
1
1
  import { type WAMessageKey } from 'baileys';
2
2
  import { EditBuilder, MessageBuilder, type DeleteOptions, type PinOptions } from '../builder/index.js';
3
3
  import { CommunityModule, GroupModule, BusinessModule, ChatModule, ContactModule, NewsletterModule, PrivacyModule, ProfileModule } from '../domain/index.js';
4
- import { type CommandHandler, type Middleware } from '../command/index.js';
4
+ import { type CommandHandler, type CommandSpec, type Middleware, type RegisteredCommand } from '../command/index.js';
5
5
  import { PresenceModule, type BroadcastOptions, type BroadcastResult, type ScheduleHandle } from '../automation/index.js';
6
6
  import type { CallPayload, MediaDownloadResult } from '../events/types.js';
7
7
  import type { AuthStoreBundle } from '../auth/types.js';
@@ -119,11 +119,15 @@ export declare class Client extends TypedEventEmitter<ClientEventMap> {
119
119
  private disconnectCloud;
120
120
  disconnect(): Promise<void>;
121
121
  logout(): Promise<void>;
122
- command(spec: string, handler: CommandHandler): this;
123
- unregisterCommand(spec: string): this;
122
+ command(spec: string | CommandSpec, handler: CommandHandler): this;
123
+ unregisterCommand(spec: string | CommandSpec): this;
124
+ /** Every registered command with its description and guards — build a help menu from this. */
125
+ commands(): RegisteredCommand[];
124
126
  use(middleware: Middleware): this;
125
127
  unuse(middleware: Middleware): this;
126
128
  private attachCommandsIfReady;
129
+ /** Admin lookup for the `admin` guard. Never throws — an unreachable group means "not an admin". */
130
+ private isGroupAdmin;
127
131
  private buildCommandContext;
128
132
  private detachCommands;
129
133
  send(to: string): MessageBuilder<'init'>;
@@ -6,7 +6,8 @@ import type { OperationGuardOptions } from '../automation/operation-guard.js';
6
6
  import type { PresenceThrottleOptions } from '../automation/presence.js';
7
7
  import type { AuthGuardOptions } from '../connection/auth-guard.js';
8
8
  import type { DisconnectReasonDomain } from '../connection/disconnect-reason.js';
9
- import type { CitationConfig } from '../events/context.js';
9
+ import type { CommandBlockedReason, CommandContext } from '../command/types.js';
10
+ import type { CitationConfig, MessageContext } from '../events/context.js';
10
11
  import type { InboundEventMap } from '../events/types.js';
11
12
  import type { MessageStore } from '../store/types.js';
12
13
  import type { PluginsOptions } from '../plugin/types.js';
@@ -109,6 +110,25 @@ export type ConnectionEventMap = {
109
110
  sessionId: string;
110
111
  error: Error;
111
112
  };
113
+ /** A command matched but a guard stopped it. zaileys sends nothing — reply here if you want to. */
114
+ 'command-blocked': {
115
+ command: string;
116
+ reason: CommandBlockedReason;
117
+ /** Seconds until the sender may retry. Only present when `reason` is `cooldown`. */
118
+ retryIn?: number;
119
+ ctx: CommandContext;
120
+ };
121
+ /** A command handler threw. Listening takes ownership: zaileys stops logging it as an error. */
122
+ 'command-error': {
123
+ command: string;
124
+ error: unknown;
125
+ ctx: CommandContext;
126
+ };
127
+ /** A prefixed message matched no command. Only emitted when something is listening. */
128
+ 'command-not-found': {
129
+ command: string;
130
+ message: MessageContext;
131
+ };
112
132
  /** Cloud provider: delivery lifecycle of outbound messages (sent/delivered/read/failed). */
113
133
  'message-status': CloudStatusEvent;
114
134
  /** Cloud provider: template review lifecycle (APPROVED/REJECTED/PAUSED...). */
@@ -1,5 +1,6 @@
1
1
  import type { Logger } from '../client/types.js';
2
2
  import type { MessageContext } from '../events/context.js';
3
+ import { type GuardBlock } from './guards.js';
3
4
  import type { CommandRegistry } from './registry.js';
4
5
  import type { CommandContext, Middleware } from './types.js';
5
6
  export interface ResolvedCommand {
@@ -16,6 +17,11 @@ export interface DispatcherDeps {
16
17
  onText: (handler: (msg: MessageContext) => void) => () => void;
17
18
  buildContext: (resolved: ResolvedCommand, msg: MessageContext) => CommandContext;
18
19
  logger: Logger;
20
+ isAdmin?: (groupJid: string, senderJid: string) => Promise<boolean>;
21
+ onBlocked?: (block: GuardBlock, ctx: CommandContext) => void;
22
+ /** Return `true` to claim the failure; otherwise the dispatcher logs it. */
23
+ onError?: (error: unknown, ctx: CommandContext) => boolean;
24
+ onNotFound?: (name: string, msg: MessageContext) => void;
19
25
  }
20
26
  export interface DispatcherHandle {
21
27
  detach(): void;
@@ -0,0 +1,17 @@
1
+ import type { CommandBlockedReason, CommandContext, CommandGuards } from './types.js';
2
+ export interface GuardBlock {
3
+ reason: CommandBlockedReason;
4
+ /** Seconds left before the sender may retry. Only set for `cooldown`. */
5
+ retryIn?: number;
6
+ }
7
+ export interface GuardDeps {
8
+ /** Resolves whether the sender is an admin of the group the command ran in. */
9
+ isAdmin: (groupJid: string, senderJid: string) => Promise<boolean>;
10
+ now: () => number;
11
+ }
12
+ export declare const resetCooldowns: () => void;
13
+ /**
14
+ * Checks a command's guards. Returns the block, or `null` when the handler may run. Nothing is sent
15
+ * to the chat here — the caller emits `command-blocked` so the wording stays the bot author's.
16
+ */
17
+ export declare const checkGuards: (guards: CommandGuards, ctx: CommandContext, deps: GuardDeps) => Promise<GuardBlock | null>;
@@ -3,5 +3,6 @@ export * from './errors.js';
3
3
  export { parseCommand } from './parser.js';
4
4
  export { CommandRegistry } from './registry.js';
5
5
  export { runMiddleware } from './middleware.js';
6
+ export { checkGuards, resetCooldowns, type GuardBlock } from './guards.js';
6
7
  export { attachCommandDispatcher } from './dispatcher.js';
7
8
  export type { DispatcherDeps, DispatcherHandle, ResolvedCommand } from './dispatcher.js';
@@ -1,13 +1,15 @@
1
- import type { CommandDefinition, CommandHandler, ParsedArgs } from './types.js';
1
+ import type { CommandDefinition, CommandHandler, CommandSpec, ParsedArgs, RegisteredCommand } from './types.js';
2
2
  export declare class CommandRegistry {
3
3
  private readonly paths;
4
4
  private readonly defs;
5
5
  private maxDepth;
6
- register(spec: string, handler: CommandHandler): void;
6
+ register(spec: string | CommandSpec, handler: CommandHandler): void;
7
7
  resolve(parsed: ParsedArgs): {
8
8
  def: CommandDefinition;
9
9
  args: string[];
10
10
  } | undefined;
11
- unregister(spec: string): void;
11
+ unregister(spec: string | CommandSpec): void;
12
12
  list(): CommandDefinition[];
13
+ /** The registered commands without their handlers — what a help menu is built from. */
14
+ describe(): RegisteredCommand[];
13
15
  }
@@ -22,9 +22,43 @@ export interface CommandContext extends MessageContext {
22
22
  }
23
23
  export type CommandHandler = (ctx: CommandContext) => Promise<void> | void;
24
24
  export type Middleware = (ctx: CommandContext, next: () => Promise<void>) => Promise<void> | void;
25
+ /** Descriptive fields a command carries so a help menu can be generated instead of hand-written. */
26
+ export interface CommandMeta {
27
+ description?: string;
28
+ /** Argument hint shown next to the name, e.g. `<@user>`. */
29
+ usage?: string;
30
+ /** Menu grouping. Plugins default it to the folder the file sits in. */
31
+ category?: string;
32
+ /** Keeps the command callable but leaves it out of listings. */
33
+ hidden?: boolean;
34
+ /** Anything else you want to hang off the command; zaileys never reads it. */
35
+ metadata?: Record<string, unknown>;
36
+ }
37
+ /** Conditions checked before a handler runs. A blocked command emits `command-blocked` instead. */
38
+ export interface CommandGuards {
39
+ /** Only runs inside a group. */
40
+ group?: boolean;
41
+ /** Only runs in a one-to-one chat. */
42
+ private?: boolean;
43
+ /** Only runs for a group admin. Implies `group`. */
44
+ admin?: boolean;
45
+ /** Seconds the same sender must wait before reusing this command. */
46
+ cooldown?: number;
47
+ }
48
+ export interface CommandSpec extends CommandMeta, CommandGuards {
49
+ name: string;
50
+ aliases?: string[];
51
+ }
52
+ /** A command as registered, with its guards and metadata resolved. */
53
+ export interface RegisteredCommand extends CommandMeta, CommandGuards {
54
+ name: string;
55
+ aliases: string[];
56
+ }
57
+ export type CommandBlockedReason = 'group-only' | 'private-only' | 'admin-only' | 'cooldown';
25
58
  export interface CommandDefinition {
26
59
  name: string;
27
60
  aliases: string[];
28
61
  parts: string[];
29
62
  handler: CommandHandler;
63
+ meta: CommandMeta & CommandGuards;
30
64
  }
@@ -19,15 +19,11 @@ export interface MediaAttachment {
19
19
  fileName: string | null;
20
20
  fileSize: number | null;
21
21
  ptt: boolean;
22
- /** Playback length in seconds, for audio and video. */
22
+ isAnimated: boolean;
23
23
  duration: number | null;
24
24
  width: number | null;
25
25
  height: number | null;
26
- /** Page count, for documents. */
27
26
  pages: number | null;
28
- /** `true` for an animated sticker or a GIF-playback video. */
29
- isAnimated: boolean;
30
- /** Inline JPEG preview, when WhatsApp sent one. Lets you show a thumbnail without downloading. */
31
27
  thumbnail: Buffer | null;
32
28
  buffer(): Promise<Buffer>;
33
29
  stream(): Promise<Readable>;
@@ -187,7 +183,6 @@ export interface MessageContext {
187
183
  senderId: string;
188
184
  senderLid: string | null;
189
185
  senderName: string | null;
190
- /** WhatsApp username (the `@handle`), without the `@`. Only sent for accounts that set one. */
191
186
  senderUsername: string | null;
192
187
  senderDevice: SenderDevice;
193
188
  timestamp: number;
@@ -201,20 +196,7 @@ export interface MessageContext {
201
196
  isViewOnce: boolean;
202
197
  isEphemeral: boolean;
203
198
  isForwarded: boolean;
204
- /** `true` for a backlog message replayed after a reconnect. Skip these to avoid answering twice. */
205
- isOffline: boolean;
206
- /** How many hops this message has been forwarded. `>= 5` is WhatsApp's "forwarded many times". */
207
- forwardCount: number;
208
- /** The chat's disappearing timer in seconds, or `null` when messages are kept. */
209
- ephemeralDuration: number | null;
210
- /** Whether WhatsApp addressed this message by phone number or by LID. */
211
- addressingMode: 'pn' | 'lid';
212
- /** JIDs of groups tagged in the message (community `@group` mentions). */
213
- mentionedGroups: string[];
214
- /** Present only on the first message of a chat opened from a Meta ad. */
215
- ad?: AdAttribution;
216
- /** Present only when the sender is a verified WhatsApp Business account. */
217
- business?: BusinessInfo;
199
+ isOld: boolean;
218
200
  isQuestion: boolean;
219
201
  isPrefix: boolean;
220
202
  isTagMe: boolean;
@@ -229,6 +211,12 @@ export interface MessageContext {
229
211
  isGroupStatusMention: boolean;
230
212
  isGroupStatus: boolean;
231
213
  isStory: boolean;
214
+ forwardCount: number;
215
+ ephemeralDuration: number | null;
216
+ addressingMode: 'pn' | 'lid';
217
+ mentionedGroups: string[];
218
+ ad?: AdAttribution;
219
+ business?: BusinessInfo;
232
220
  roomName(): Promise<string | null>;
233
221
  receiverName(): Promise<string | null>;
234
222
  media?: ContextMedia;
@@ -262,7 +250,7 @@ export interface BuildContextInput {
262
250
  isForwarded: boolean;
263
251
  isBroadcast: boolean;
264
252
  isNewsletter: boolean;
265
- isOffline?: boolean;
253
+ isOld?: boolean;
266
254
  forwardCount?: number;
267
255
  ephemeralDuration?: number | null;
268
256
  addressingMode?: 'pn' | 'lid';
@@ -8,7 +8,7 @@ export interface DecodeContext {
8
8
  selfName?: string;
9
9
  lidMap?: Map<string, string>;
10
10
  /** WhatsApp replays a reconnect's backlog as `append`; live traffic arrives as `notify`. */
11
- isOffline?: boolean;
11
+ isOld?: boolean;
12
12
  logger?: DownloadLogger;
13
13
  channelId?: string;
14
14
  receiverId?: string;