Skip to content

Commit 3e59762

Browse files
committed
chore: release v4.8.4
1 parent 33ef8c5 commit 3e59762

5 files changed

Lines changed: 143 additions & 131 deletions

File tree

.changeset/auto-reject-call.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# zaileys
22

3+
## 4.8.4
4+
5+
### Patch Changes
6+
7+
- bd6be18: Auto-reject incoming calls. New `autoRejectCall` client option (`true`, or `{ enabled, allow, onReject }`) hangs up incoming WhatsApp calls automatically — with an allow-list (jid/digits array or predicate) and an `onReject` hook to notify the caller. The underlying `client.rejectCall(call)` / `client.rejectCall(callId, from)` method is exposed for manual control from a `call-incoming` handler. Off by default; unofficial provider only (the Cloud API has no calls — it throws `UNSUPPORTED_ON_CLOUD`).
8+
39
## 4.8.3
410

511
### Patch Changes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "zaileys",
33
"description": "Zaileys - Simplified WhatsApp Node.js TypeScript/JavaScript API",
4-
"version": "4.8.3",
4+
"version": "4.8.4",
55
"license": "MIT",
66
"type": "module",
77
"main": "./dist/index.cjs",

tasks/plan.md

Lines changed: 122 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,153 @@
1-
# Plan — Full rewrite of the zaileys Agent Skill suite (provider-aware)
1+
# Plan — Auto-reject calls (`autoRejectCall` config + `client.rejectCall()` method)
2+
3+
Team ask: auto-reject incoming WhatsApp calls, **toggleable via `Client` config**, and **expose the
4+
method** so it can be called manually. Then update docs.
5+
6+
## Findings (read-only investigation)
7+
8+
- **Primitive exists**: baileys exposes `rejectCall(callId: string, callFrom: string): Promise<void>`
9+
(`node_modules/baileys/lib/Socket/index.d.ts:66`). No wrapper in zaileys today (`grep rejectCall src/` = 0).
10+
- **Call events already wired**: `call-incoming` / `call-ended` emit `CallPayload`
11+
(`{ callId, from, isGroup, isVideo, timestamp, status }`) — `src/events/decoders/calls.ts`,
12+
`src/events/pipeline.ts:274`.
13+
- **Toggle pattern to mirror**: `autoDelete?: AutoDeleteOptions | false` in `ClientOptions`
14+
`AutoDeleteSweeper` module in `src/automation/`, constructed in the Client ctor, started on connect,
15+
stopped on close. `PresenceModule` shows the options+method shape.
16+
- **Provider dimension (blast radius)**: calls are **WhatsApp-Web-only**. The Cloud API provider has no
17+
call events, so `rejectCall()` must throw `ZaileysProviderError('UNSUPPORTED_ON_CLOUD')` like
18+
`edit`/`delete`/`pin`, and `autoRejectCall` must be inert on cloud (never wired).
19+
20+
## Design (decided)
21+
22+
```ts
23+
// config — OFF by default (rejecting calls is destructive; must be opt-in)
24+
new Client({ autoRejectCall: true })
25+
new Client({ autoRejectCall: { enabled: true, allow: ['628owner@s.whatsapp.net'], onReject: (call) => … } })
26+
27+
// method — accepts the event payload OR raw ids
28+
await client.rejectCall(call) // call = CallPayload from 'call-incoming'
29+
await client.rejectCall(callId, from)
30+
```
231

3-
Source of truth: [SPEC.md](../SPEC.md). Goal: **full rewrite** of every skill file so both providers
4-
(🔗 unofficial / ☁️ official Cloud API) are native throughout — not appended. Canonical lives in
5-
`skills/*`; `plugins/zaileys-official/skills/*` is generated by `npm run skill:sync` (never hand-edit).
32+
`AutoRejectCallOptions = { enabled?: boolean; allow?: string[] | ((jid: string) => boolean | Promise<boolean>); onReject?: (call) => void | Promise<void> }`
33+
`allow` = whitelist (skip rejecting these callers, mirrors the `citation` predicate style);
34+
`onReject` = post-reject hook (e.g. send "sorry, calls not supported"). Keep it lean — no
35+
video/voice/group sub-filters until asked (`isVideo`/`isGroup` are already on the payload, so users
36+
can filter inside `allow`).
637

7-
## Files & dependency graph
38+
## Dependency graph
839

940
```
10-
┌───────────────────────────────┐
11-
│ T1 references/cloud.md (NEW) │ ← single source of cloud truth
12-
│ T2 references/api.md (+provider)│ everything else cites these
13-
└───────────────┬───────────────┘
14-
┌────────────────────────┼────────────────────────┐
15-
▼ ▼ ▼
16-
┌───────────────┐ ┌───────────────────────┐ ┌──────────────────────┐
17-
│ T3 assist │ │ T4 errors T5 pitfalls│ │ T8 scaffold │
18-
│ SKILL.md │ │ T6 recipes T7 trouble │ │ T9 debug T10 review │
19-
└───────┬───────┘ └───────────┬───────────┘ └──────────┬───────────┘
20-
└───────────────────────┼──────────────────────────┘
21-
22-
┌──────────────────────────┐
23-
│ T11 skill:sync + verify │ ← mirror + diff + snippet audit
24-
└──────────────────────────┘
41+
┌──────────────────────────────────────┐
42+
│ T1 automation/auto-reject-call.ts │ module: options, allow-predicate, reject+hook
43+
│ + unit tests │ (pure, socket injected)
44+
└───────────────┬──────────────────────┘
45+
46+
┌──────────────────────────────────────┐
47+
│ T2 Client wiring │ ClientOptions.autoRejectCall, ctor, attach on
48+
│ + client.rejectCall() + guard │ connect (baileys only), web-only guard, exports
49+
└───────────────┬──────────────────────┘
50+
┌───────┴────────┐
51+
▼ ▼
52+
┌───────────────┐ ┌──────────────────┐
53+
│ T3 docs site │ │ T4 skill suite │ (independent of each other)
54+
└───────┬───────┘ └────────┬─────────┘
55+
└────────┬──────────┘
56+
57+
┌───────────────┐
58+
│ T5 changeset │ minor + release-ready
59+
└───────────────┘
2560
```
2661

27-
- **T1 (cloud.md) + T2 (api.md provider section) block everything** — they are the referenced truth.
28-
- T3 (orchestrator) references all references incl. cloud.md.
29-
- T4–T7 (references) each cite cloud.md for cloud detail; independent of each other.
30-
- T8–T10 (sibling skills) cite cloud.md + recipes; independent of each other.
31-
- T11 (sync + verify) is last — mirrors canonical to the plugin copy and audits.
62+
- **T1 blocks T2** (Client constructs the module).
63+
- **T3 / T4 need T2** (document the shipped API); independent of each other.
64+
- **T5** last.
3265

3366
## Phases & checkpoints
3467

35-
- **Phase A = T1 + T2.** → 🚩 CP1: cloud reference complete + api.md provider section; every snippet valid vs `src/cloud/*` and 4.8.1.
36-
- **Phase B = T3 + T4 + T5 + T6 + T7.** → 🚩 CP2: orchestrator + all 6 references provider-aware; routing covers every cloud intent.
37-
- **Phase C = T8 + T9 + T10.** → 🚩 CP3: scaffold/debug/review all provider-aware.
38-
- **Phase D = T11.** → 🚩 CP4: `diff -r skills plugins/.../skills` clean; ready to commit.
39-
40-
Slicing note: these are docs, so each task = **one file rewritten completely with both providers**,
41-
ordered by dependency (the referenced `cloud.md` first). Each task is independently verifiable and
42-
leaves the suite coherent.
68+
- **Phase A = T1 + T2.** → 🚩 CP1: feature works + guarded; full suite green; typecheck clean.
69+
- **Phase B = T3 + T4.** → 🚩 CP2: docs site builds; skill synced (`diff -r` clean).
70+
- **Phase C = T5.** → 🚩 CP3: changeset in; ready to release (publish = user's call).
4371

4472
---
4573

4674
## Tasks
4775

48-
### Phase A — Cloud source of truth
49-
50-
**T1 — `references/cloud.md` (NEW).** The self-contained official-provider reference.
51-
Sections: provider switch + `CloudOptions` (accessToken/phoneNumberId/wabaId/verifyToken/appSecret/
52-
apiVersion/baseUrl); `connect()` = health-check (no socket/QR); `webhook()` — framework-agnostic
53-
handler + Next/Hono/Express/node:http mounts + Meta dashboard setup; **sending differences** (shared
54-
builder → cite api.md; cloud caveats: buttons max 3, no carousel/poll, `markRead(id,{typing})`,
55-
contact first-name, media sizes); `sendTemplate(to,name,lang,components)`; OTP create+send; marketing
56-
campaigns; **full `wa.cloud.*`** (templates CRUD+get, profile, info, phoneNumbers, flows, commerce
57-
catalogs/products/send, blocklist, qr, analytics, phone, sendAddressRequest); cloud events
58-
(`message-status`/`template-status`/`flow-response`/`order`); the 24-hour window; limits & gotchas
59-
with fixes; `UNSUPPORTED_ON_CLOUD` for web-only.
60-
- *Accept*: every method/param matches `src/cloud/*` (the live-tested surface); no invented API;
61-
web-only features stated as ❌ with the fix.
62-
- *Verify*: grep each `wa.cloud.X`/`send*` against `src/cloud/module.ts`, `transport.ts`,
63-
`client.ts`; snippets compile-plausible.
64-
65-
**T2 — `references/api.md` provider section.** Add a top-of-file **Providers** subsection (`provider`
66-
option, cloud config shape, "shared vs cloud-only", link to cloud.md). Mark the web-only domain
67-
namespaces (`group`/`newsletter`/`community`/`privacy`/`presence`, `edit`/`delete`/`pin`) as
68-
**unofficial-only — throw `UNSUPPORTED_ON_CLOUD` on cloud**.
69-
- *Accept*: reader knows provider exists before the builder section; web-only surfaces flagged.
70-
- *Verify*: cross-check the guard list against `assertWebProvider` sites in `src/client/client.ts`.
76+
### T1 — `src/automation/auto-reject-call.ts` + unit tests
7177

72-
🚩 **CP1** — cloud reference + api provider section accurate & cross-linked.
78+
Module owning the policy. Socket + logger injected (testable, no Client dependency).
7379

74-
### Phase B — Orchestrator + references
75-
76-
**T3 — `zaileys-assist/SKILL.md` full rewrite.** Provider-aware intro ("two providers, one API,
77-
baileys default"); routing table gains rows for cloud/webhook/template/OTP/campaign/Flows/commerce/
78-
`wa.cloud.*` → cloud.md; mental model adds the provider dimension (cloud = token auth + webhook, no
79-
socket); golden rules provider-aware (cold send needs template; web-only throws on cloud); references
80-
list includes cloud.md.
81-
- *Accept*: any cloud intent routes to cloud.md; baileys stays the default framing.
82-
- *Verify*: every cloud intent in SPEC §2.1 has a routing row.
83-
84-
**T4 — `references/errors.md`.** Add `ZaileysCloudError` (CONFIG/AUTH/REQUEST_FAILED/RATE_LIMITED/
85-
NOT_IMPLEMENTED), `ZaileysProviderError` (UNSUPPORTED_ON_CLOUD), + Graph codes (131047, 132000,
86-
131009, 190, 131026, 131056) with cause→fix. Keep existing baileys error classes.
87-
- *Verify*: codes match `src/cloud/errors.ts` + the docs error table.
80+
```ts
81+
export interface AutoRejectCallOptions { enabled?: boolean; allow?: …; onReject?: … }
82+
export interface CallSocketLike { rejectCall(callId: string, callFrom: string): Promise<void> }
83+
export class AutoRejectCallModule {
84+
constructor(getSocket: () => CallSocketLike | undefined, options: AutoRejectCallOptions, logger?)
85+
reject(callId: string, from: string): Promise<void> // raw reject (used by the public method)
86+
handle(call: CallPayload): Promise<void> // policy: allow-check → reject → onReject
87+
}
88+
```
8889

89-
**T5 — `references/pitfalls.md`.** Add cloud anti-patterns → correct way: cold send w/o template
90-
(131047); param count mismatch (132000); `rich:true` on cloud (web-only); calling `group`/`newsletter`
91-
on cloud; forgetting the webhook; body-parser eating the raw body; not making inbound idempotent.
90+
- *Accept*: `handle()` rejects a call when enabled; **skips** when the caller matches `allow`
91+
(array or predicate); runs `onReject` **after** a successful reject; a throwing `onReject` or
92+
`rejectCall` is logged, never crashes the client; `reject()` throws a typed error when there's no
93+
socket (not connected).
94+
- *Verify*: `tests/automation/auto-reject-call.test.ts` — enabled/disabled, allow-array, allow-predicate
95+
(sync+async), onReject called once with the payload, reject failure swallowed+logged, no-socket error.
9296

93-
**T6 — `references/recipes.md`.** Add runnable cloud recipes: cloud echo bot + webhook (Next/Hono/
94-
Express), send OTP (create+send), marketing campaign (create→approve→broadcast→track), receive
95-
`order`/`flow-response`.
96-
- *Verify*: recipes use only real 4.8.1 API.
97+
### T2 — Client wiring + public `rejectCall()` + guards
9798

98-
**T7 — `references/troubleshooting.md`.** Add cloud runtime symptoms: webhook verify 403 / signature
99-
401, events not firing (webhook needs no connect() but must be mounted; check subscribe `messages`),
100-
`131047` outside window, token expired 190.
99+
- `ClientOptions.autoRejectCall?: boolean | AutoRejectCallOptions` (default **off**), normalized in the
100+
ctor (`true``{ enabled: true }`).
101+
- Construct the module; **attach to `call-incoming` only on the baileys provider** when enabled
102+
(wire where the inbound pipeline is attached; detach on close alongside the other handles).
103+
- Public method `client.rejectCall(callOrId: CallPayload | string, from?: string): Promise<void>`
104+
`assertWebProvider('rejectCall')` first (throws `UNSUPPORTED_ON_CLOUD` on cloud), then delegate.
105+
- Export `AutoRejectCallOptions` from `src/automation/index.ts` (and thus the package root).
106+
- *Accept*: `new Client({ autoRejectCall: true })` auto-rejects an emitted `call-incoming`;
107+
default (unset) does **not** reject; `client.rejectCall(call)` and `client.rejectCall(id, from)` both
108+
work; on `provider:'cloud'` the method throws `UNSUPPORTED_ON_CLOUD` and the config is never wired;
109+
handlers detach on disconnect (no double-reject after reconnect).
110+
- *Verify*: `tests/client/auto-reject-call.test.ts` with the existing mock socket
111+
(`makeIntegrationSocket` + `triggerCall`-style ev emit): toggle on/off, both method arities, cloud
112+
guard, allow-list end-to-end. Then `pnpm test` (full suite) + `npx tsc --noEmit`.
101113

102-
🚩 **CP2**orchestrator + all references provider-aware; routing complete.
114+
🚩 **CP1**feature + guard done; 2482+ tests green; typecheck clean.
103115

104-
### Phase C — Sibling skills
116+
### T3 — Docs site
105117

106-
**T8 — `zaileys-scaffold/SKILL.md`.** Ask **which provider** first; scaffold a cloud project variant
107-
(client + `webhook()` route + `.env` for token/phone/verify/appSecret) alongside the baileys variant.
108-
Description mentions cloud/official/webhook.
118+
- `docs/content/configuration.mdx` — add the `autoRejectCall` row/section (type, default `false`,
119+
options table, examples incl. `allow` + `onReject`).
120+
- `docs/content/events.mdx` — under the call events, cross-link auto-reject + `client.rejectCall()`.
121+
- `docs/content/api-reference.mdx` — add `rejectCall` to the client-methods list.
122+
- `docs/content/providers.mdx` + `docs/content/official/limits.mdx` — mark calls / `rejectCall` as
123+
**🔗 unofficial-only** (they don't exist on the Cloud API).
124+
- *Verify*: `cd docs && npm run build` → clean; `out/index.html` present.
109125

110-
**T9 — `zaileys-debug/SKILL.md`.** Add cloud error taxonomy + webhook symptoms to the doctor;
111-
description mentions cloud error codes.
126+
### T4 — Skill suite (canonical `skills/*`, then sync)
112127

113-
**T10 — `zaileys-review/SKILL.md`.** Add cloud checks: template-gated cold sends, webhook signature +
114-
idempotency, no web-only calls on cloud, secret handling. Description mentions cloud review.
128+
- `references/api.md``autoRejectCall` in the ClientOptions table; `rejectCall` in client methods;
129+
flag as unofficial-only.
130+
- `references/recipes.md` — a short "auto-reject calls" recipe (config + manual + notify-caller hook).
131+
- `zaileys-review/SKILL.md` quick-cues — `rejectCall` in the method list.
132+
- `npm run skill:sync``diff -r skills plugins/zaileys-official/skills` clean.
133+
- *Verify*: snippets match the shipped API exactly (grep against `src/`); diff clean.
115134

116-
🚩 **CP3**all four skills provider-aware.
135+
🚩 **CP2**docs build green; skill synced.
117136

118-
### Phase D — Sync + verify
137+
### T5 — Changeset
119138

120-
**T11 — `npm run skill:sync` + verify.** Mirror canonical → plugin copy. `diff -r skills
121-
plugins/zaileys-official/skills` = clean (minus intentional plugin-only files). Audit: every cloud
122-
snippet valid vs `src/cloud/*`; routing covers all cloud intents; unofficial content un-regressed.
123-
- *Verify*: `diff -r` empty; spot-check 5 cloud snippets against source.
139+
- `.changeset/*.md` **minor** (additive feature): config `autoRejectCall` + `client.rejectCall()`.
140+
- *Verify*: `npx changeset version` dry-sane (do NOT version/publish without the user's go-ahead).
124141

125-
🚩 **CP4**plugin copy matches canonical; ready to commit (release/publish = ask user).
142+
🚩 **CP3** — ready to release.
126143

127144
## Risks
128145

129-
- **Snippet drift** — inventing Cloud-API methods. Mitigate: grep every call against `src/cloud/*`
130-
before writing; T1 is the vetted source all others cite.
131-
- **Sync drift** — hand-editing the plugin copy. Mitigate: edit only `skills/*`, T11 regenerates.
132-
- **Over-duplication** — re-teaching shared builder per provider. Mitigate: cloud.md documents only
133-
differences + cloud-exclusive; cites api.md for shared.
146+
- **Destructive default** — auto-rejecting calls without opt-in would surprise users. Mitigate: default
147+
**off**; `true` must be explicit.
148+
- **Double-reject / leaks after reconnect** — the `call-incoming` handler must be detached with the
149+
other pipeline handles. Mitigate: attach/detach beside the existing `inboundHandle` lifecycle; test
150+
reconnect.
151+
- **Provider drift** — calls are web-only; forgetting the guard means a confusing cloud crash.
152+
Mitigate: `assertWebProvider` + a cloud-guard test (T2), docs/skill flagged (T3/T4).
153+
- **Hook failures** — a user `onReject` that throws must not kill the client. Mitigate: catch+log (T1).

0 commit comments

Comments
 (0)