@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.
- package/README.md +12 -11
- package/index.js +41 -7
- 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
|
|
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
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
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);
|