@laburen/openclaw-plugin-whatsapp-api 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +12 -11
  2. package/index.js +41 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,17 +7,6 @@ WhatsApp Cloud API channel for OpenClaw: your router receives Meta webhooks and
7
7
  ```bash
8
8
  openclaw plugins install @laburen/openclaw-plugin-whatsapp-api
9
9
  ```
10
-
11
- If you install from a local folder instead, copy the package into your OpenClaw extensions root and enable it in config (same plugin id: `whatsapp-api`).
12
-
13
- ## Context window reminder (cron scripts)
14
-
15
- The plugin stores the last inbound message so **`check.sh`** can remind the user before Meta’s 24-hour window ends. Run **`setup.sh`** by path (no `cd` needed—the script resolves `check.sh` next to itself):
16
-
17
- ```bash
18
- bash ~/.openclaw/extensions/whatsapp-api/src/scripts/setup.sh
19
- ```
20
-
21
10
  ## Setup
22
11
 
23
12
  1. In [Meta for Developers](https://developers.facebook.com/), create or open an app with **WhatsApp** product enabled and note your **Phone number ID** and a long-lived **System User** or **Temporary** access token with `whatsapp_business_messaging` (and webhook permissions as required by your setup).
@@ -55,6 +44,18 @@ openclaw gateway restart
55
44
 
56
45
  - Transient failures (`429`, `5xx`, timeouts) are retried according to `maxRetries`, `retryBackoffMs`, and `requestTimeoutMs`.
57
46
 
47
+ ### WhatsApp 24-hour context window (reminder)
48
+
49
+ WhatsApp Cloud API lets you send **session** messages (normal replies) only within about **24 hours** of the user’s last inbound message. After that window, outbound traffic is limited to **approved templates** until the user writes again.
50
+
51
+ This plugin can nudge you before the window closes:
52
+
53
+ 1. **Snapshot on inbound:** For each user message handled by the channel, a hook writes `<OpenClaw state dir>/whatsapp-api/last-inbound-message.json` with `from`, `content`, `timestamp`, channel/account/conversation ids when present, and **`notified: false`**. That reset means a new user message clears any previous “we already warned” state.
54
+ 2. **Cron from the host:** On service start, the plugin runs `setup.sh` (shipped under `src/scripts/` in the npm package). The script registers an OpenClaw cron job that periodically invokes `check.sh` via the exec tool (see the job’s description in `setup.sh`).
55
+ 3. **`check.sh` behavior:** It reads the JSON file, skips if missing or `notified` is already `true`, and compares elapsed time since `timestamp` to a threshold (default **23 hours**). If the threshold is met, it runs `openclaw message send` to the user’s phone (parsed from `from`) with a configurable warning text, then sets **`notified: true`** in the same file so the warning is not sent again until the next inbound message.
56
+
57
+ Useful environment variables for the scripts: `OPENCLAW_DIR` (defaults to `/home/<user>/.openclaw`), `THRESHOLD_MINUTES`, `WARNING_MESSAGE`, and `EVERY` for the cron interval when installing (default `30m`). To remove the job: `bash /path/to/node_modules/@laburen/openclaw-plugin-whatsapp-api/src/scripts/setup.sh --uninstall` (adjust path to your install).
58
+
58
59
  **Inbound webhook (from your router to OpenClaw)**
59
60
 
60
61
  | Item | Detail |
package/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import fs, { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { tmpdir } from "node:os";
4
+ import { spawn } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
4
6
  //#region src/core/accounts.ts
5
7
  const CHANNEL_ID$1 = "whatsapp-api";
6
8
  const DEFAULT_WEBHOOK_PATH = "/webhook/whatsapp-api";
@@ -95,9 +97,12 @@ const MIME_BY_EXT = {
95
97
  ".m4a": "audio/mp4",
96
98
  ".pdf": "application/pdf",
97
99
  ".txt": "text/plain",
100
+ ".md": "text/markdown",
98
101
  ".csv": "text/csv",
99
102
  ".json": "application/json",
100
- ".zip": "application/zip"
103
+ ".zip": "application/zip",
104
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
105
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
101
106
  };
102
107
  function normalizeMime(value) {
103
108
  const trimmed = value?.trim();
@@ -1260,6 +1265,19 @@ function createWhatsAppApiChannel(api) {
1260
1265
  account: resolveAccount(cfg, accountId ?? null)
1261
1266
  })
1262
1267
  };
1268
+ },
1269
+ sendMedia: async ({ to, mediaUrl, text, cfg, accountId }) => {
1270
+ const account = resolveAccount(cfg, accountId ?? null);
1271
+ return {
1272
+ channel: CHANNEL_ID,
1273
+ chatId: to,
1274
+ messageId: await sendWhatsAppApiMedia({
1275
+ to,
1276
+ mediaUrl,
1277
+ text: text || void 0,
1278
+ account
1279
+ })
1280
+ };
1263
1281
  }
1264
1282
  },
1265
1283
  gateway: {
@@ -1388,7 +1406,7 @@ async function writeLastInboundMessage(stateDir, data) {
1388
1406
  /**
1389
1407
  * SPDX-License-Identifier: MIT
1390
1408
  *
1391
- * Persist last inbound message snapshot (used by background services).
1409
+ * Persist last inbound message snapshot for `check.sh` / cron window alerts.
1392
1410
  */
1393
1411
  const log$2 = waLogger("message-received/persist-last-inbound");
1394
1412
  async function handlePersistLastInbound(event, ctx) {
@@ -1430,17 +1448,33 @@ function registerAllPluginHooks(api) {
1430
1448
  //#endregion
1431
1449
  //#region src/services/context-window-check/register.ts
1432
1450
  const log$1 = waLogger("context-window-check");
1433
- /**
1434
- * Registers the `whatsapp-api` plugin service (state dir wiring; see `start` for
1435
- * the commented in-process poll vs OpenClaw cron + scripts, described in the
1436
- * module comment above).
1437
- */
1451
+ const SETUP_SH = path.join(path.dirname(fileURLToPath(import.meta.url)), "src", "scripts", "setup.sh");
1452
+ function runSetupSh() {
1453
+ return new Promise((resolve, reject) => {
1454
+ const child = spawn("bash", [SETUP_SH], {
1455
+ stdio: "inherit",
1456
+ env: process.env
1457
+ });
1458
+ child.on("error", reject);
1459
+ child.on("close", (code, signal) => {
1460
+ if (code === 0) resolve();
1461
+ else reject(/* @__PURE__ */ new Error(`setup.sh exited with code ${code ?? signal}`));
1462
+ });
1463
+ });
1464
+ }
1465
+ /** Registers the `whatsapp-api` plugin service (state dir + cron setup). */
1438
1466
  function registerContextWindowCheckService(api) {
1439
1467
  api.registerService({
1440
1468
  id: "whatsapp-api",
1441
1469
  start: async (ctx) => {
1442
1470
  setPluginServiceStateDir(ctx.stateDir);
1443
1471
  log$1.info("service started");
1472
+ try {
1473
+ await runSetupSh();
1474
+ log$1.info("setup.sh finished");
1475
+ } catch (error) {
1476
+ log$1.error("setup.sh failed", { error: String(error) });
1477
+ }
1444
1478
  },
1445
1479
  stop: async (_ctx) => {
1446
1480
  setPluginServiceStateDir(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laburen/openclaw-plugin-whatsapp-api",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "WhatsApp API channel plugin for OpenClaw",
6
6
  "main": "index.js",