@manybot/manybot 5.6.0 → 5.7.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/dist/drivers/baileys/adapter.js +294 -73
- package/dist/drivers/baileys/api/index.js +176 -141
- package/dist/drivers/baileys/index.js +36 -2
- package/dist/drivers/baileys/sdk/baileysSock.js +1 -1
- package/dist/drivers/whatsmeow/client.js +54 -5
- package/dist/drivers/whatsmeow/installer.js +20 -4
- package/dist/drivers/whatsmeow/supervisor.js +24 -5
- package/dist/kernel/sendFallbackGuard.js +15 -5
- package/dist/locales/en.json +3 -2
- package/dist/locales/es.json +3 -2
- package/dist/locales/pt.json +3 -2
- package/dist/main.js +23 -0
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync, chmodSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, writeFileSync, chmodSync, existsSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import * as clack from "@clack/prompts";
|
|
4
|
-
import { persistConfigValue } from "#config";
|
|
4
|
+
import { persistConfigValue, CONFIG_DIR } from "#config";
|
|
5
5
|
import { t } from "#i18n";
|
|
6
6
|
const SUPPORTED = [
|
|
7
7
|
{ os: "linux", arch: "x64", name: "whatsmeow-service-linux-x64" },
|
|
@@ -22,7 +22,7 @@ async function fetchLatestTag() {
|
|
|
22
22
|
return data.tag_name ?? "v5.6.1";
|
|
23
23
|
}
|
|
24
24
|
function binaryDir() {
|
|
25
|
-
return path.resolve(
|
|
25
|
+
return path.resolve(CONFIG_DIR, "whatsmeow-service", "bin");
|
|
26
26
|
}
|
|
27
27
|
export async function promptWhatsmeowInstall() {
|
|
28
28
|
const target = detectTarget();
|
|
@@ -30,6 +30,16 @@ export async function promptWhatsmeowInstall() {
|
|
|
30
30
|
clack.log.warn(str(t("whatsmeow.unsupportedArch", { os: process.platform, arch: process.arch })));
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
+
const outPath = path.join(binaryDir(), "whatsmeow-service");
|
|
34
|
+
// Binary already on disk → skip the prompt and the download, but
|
|
35
|
+
// make sure the config flag is set so the supervisor boots on the
|
|
36
|
+
// next run. The earlier "no, declined install" flow only writes the
|
|
37
|
+
// TOML on success, so users who later build the binary by hand also
|
|
38
|
+
// hit this branch on their next first-login.
|
|
39
|
+
if (existsSync(outPath)) {
|
|
40
|
+
await persistConfigValue("driver_whatsmeow_enabled", "true");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
33
43
|
const choice = await clack.confirm({
|
|
34
44
|
message: str(t("whatsmeow.installPrompt")),
|
|
35
45
|
initialValue: false,
|
|
@@ -61,10 +71,16 @@ export async function promptWhatsmeowInstall() {
|
|
|
61
71
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
62
72
|
const dir = binaryDir();
|
|
63
73
|
mkdirSync(dir, { recursive: true });
|
|
64
|
-
const outPath = path.join(dir, "whatsmeow-service");
|
|
65
74
|
writeFileSync(outPath, buffer);
|
|
66
75
|
chmodSync(outPath, 0o755);
|
|
67
76
|
await persistConfigValue("driver_whatsmeow_enabled", "true");
|
|
68
77
|
spin.stop(str(t("whatsmeow.installed", { path: outPath })));
|
|
69
78
|
clack.note(str(t("whatsmeow.restartNotice")), str(t("whatsmeow.installTitle")));
|
|
79
|
+
// The bot is still in its very first run (no Baileys session yet, no
|
|
80
|
+
// supervisor spawned) — the config has just been updated on disk but
|
|
81
|
+
// the in-memory `CONFIG` object and the supervisor lifecycle were
|
|
82
|
+
// initialized at startup with `whatsmeow.enabled = false`. Restarting
|
|
83
|
+
// is required for the new value to take effect. Exit cleanly so the
|
|
84
|
+
// user just re-runs the bot.
|
|
85
|
+
setTimeout(() => process.exit(0), 100);
|
|
70
86
|
}
|
|
@@ -30,12 +30,13 @@
|
|
|
30
30
|
* See the lifecycle contract this implements.
|
|
31
31
|
*/
|
|
32
32
|
import { spawn } from "node:child_process";
|
|
33
|
-
import { existsSync } from "node:fs";
|
|
33
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
34
34
|
import path from "node:path";
|
|
35
35
|
import { fileURLToPath } from "node:url";
|
|
36
|
-
import { CONFIG } from "#config";
|
|
36
|
+
import { CONFIG, CONFIG_DIR, CLIENT_ID } from "#config";
|
|
37
37
|
import { logger } from "#logger";
|
|
38
38
|
import { fireAlert } from "#kernel/alerts.js";
|
|
39
|
+
import { getDriverManager } from "#kernel/driverManager.js";
|
|
39
40
|
// ── Tunables (mirror drivers/baileys/index.ts:98-106) ──────────────────────
|
|
40
41
|
const RECONNECT_BASE_MS = 1000;
|
|
41
42
|
const RECONNECT_MAX_MS = 60_000;
|
|
@@ -49,7 +50,8 @@ const SHUTDOWN_GRACE_MS = 5_000;
|
|
|
49
50
|
* first match that exists and is a regular file. Order:
|
|
50
51
|
* 1. CONFIG.drivers.whatsmeow.binaryPath (explicit user choice)
|
|
51
52
|
* 2. env WM_BINARY_PATH (escape hatch for exotic installs)
|
|
52
|
-
* 3.
|
|
53
|
+
* 3. stable config dir (~/.manybot/whatsmeow-service/bin/whatsmeow-service)
|
|
54
|
+
* 4. dev layout (<cwd>/whatsmeow-service/bin/whatsmeow-service)
|
|
53
55
|
* 4. npm-global layout (sibling of the node binary, /usr/local style)
|
|
54
56
|
*/
|
|
55
57
|
function resolveBinaryPath() {
|
|
@@ -60,6 +62,8 @@ function resolveBinaryPath() {
|
|
|
60
62
|
const fromEnv = process.env.WM_BINARY_PATH;
|
|
61
63
|
if (fromEnv)
|
|
62
64
|
candidates.push(path.resolve(fromEnv));
|
|
65
|
+
// Stable config dir: ~/.manybot/whatsmeow-service/bin/whatsmeow-service
|
|
66
|
+
candidates.push(path.resolve(CONFIG_DIR, "whatsmeow-service", "bin", "whatsmeow-service"));
|
|
63
67
|
// Dev: `<repo>/whatsmeow-service/bin/whatsmeow-service`
|
|
64
68
|
candidates.push(path.resolve(process.cwd(), "whatsmeow-service", "bin", "whatsmeow-service"));
|
|
65
69
|
// Global npm install: `<prefix>/bin/../share/manybot/bin/whatsmeow-service`
|
|
@@ -148,8 +152,12 @@ export async function startWhatsmeowSupervisor() {
|
|
|
148
152
|
"whatsmeow-service/bin/whatsmeow-service relative to cwd. Bot will run on Baileys only.");
|
|
149
153
|
return null;
|
|
150
154
|
}
|
|
155
|
+
logger.info(`[supervisor] using binary: ${binary}`);
|
|
151
156
|
const grpcAddress = CONFIG.drivers.whatsmeow.grpcAddress || "localhost:50051";
|
|
152
|
-
const sessionDir = path.resolve(
|
|
157
|
+
const sessionDir = path.resolve(CONFIG_DIR, "sessions", CLIENT_ID, "whatsmeow", "session.db");
|
|
158
|
+
// Ensure the parent directory exists before the Go subprocess tries to
|
|
159
|
+
// create/open the SQLite file.
|
|
160
|
+
mkdirSync(path.dirname(sessionDir), { recursive: true });
|
|
153
161
|
const state = {
|
|
154
162
|
proc: null,
|
|
155
163
|
pid: null,
|
|
@@ -196,6 +204,7 @@ export async function startWhatsmeowSupervisor() {
|
|
|
196
204
|
state.halted = true;
|
|
197
205
|
state.ready = false;
|
|
198
206
|
state.readyDeferred?.reject(new Error(reason));
|
|
207
|
+
getDriverManager().markDegraded("whatsmeow", 600_000);
|
|
199
208
|
fireAlert("whatsmeow_subprocess_halted", { reason });
|
|
200
209
|
}
|
|
201
210
|
function scheduleRestart() {
|
|
@@ -220,7 +229,17 @@ export async function startWhatsmeowSupervisor() {
|
|
|
220
229
|
let proc;
|
|
221
230
|
try {
|
|
222
231
|
proc = spawn(state.binary, ["--grpc-addr", state.addr, "--session-dir", state.sessionDir], {
|
|
223
|
-
stdio: "ignore",
|
|
232
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
233
|
+
});
|
|
234
|
+
// Forward Go service stdout/stderr to the bot log so we can see
|
|
235
|
+
// crashes, missing dependencies, port-in-use, etc. Without this
|
|
236
|
+
// `stdio: "ignore"` would discard everything and a crashed
|
|
237
|
+
// subprocess would only surface as an exit code.
|
|
238
|
+
proc.stdout?.on("data", (chunk) => {
|
|
239
|
+
process.stdout.write(`[whatsmeow-stdout] ${chunk}`);
|
|
240
|
+
});
|
|
241
|
+
proc.stderr?.on("data", (chunk) => {
|
|
242
|
+
process.stderr.write(`[whatsmeow-stderr] ${chunk}`);
|
|
224
243
|
});
|
|
225
244
|
}
|
|
226
245
|
catch (e) {
|
|
@@ -81,11 +81,21 @@ export async function sendWithFallback(jid, text, opts = {}) {
|
|
|
81
81
|
// waitForSendSlot is the same throttle the rest of the senders use
|
|
82
82
|
// (fallback must respect rate-limit too).
|
|
83
83
|
await waitForSendSlot(jid, { cooldown: true, jitter: true });
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
let primaryRef = null;
|
|
85
|
+
let primarySendFailed = false;
|
|
86
|
+
try {
|
|
87
|
+
primaryRef = await primary.sendText(jid, text, opts);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
primarySendFailed = true;
|
|
91
|
+
logger.warn({ driver: primaryKey, jid, error: String(err) }, "send threw on primary");
|
|
92
|
+
}
|
|
93
|
+
if (!primarySendFailed) {
|
|
94
|
+
if (await verifyDelivery(primary, jid, primaryRef, drivers.verifyWindowMs)) {
|
|
95
|
+
return primaryRef;
|
|
96
|
+
}
|
|
97
|
+
logger.warn({ driver: primaryKey, jid, messageId: primaryRef.id }, "send not confirmed by primary");
|
|
87
98
|
}
|
|
88
|
-
logger.warn({ driver: primaryKey, jid, messageId: ref.id }, "send not confirmed by primary");
|
|
89
99
|
dm.markDegraded(primaryKey, drivers.fallbackCooldownMs);
|
|
90
100
|
const secondary = pickSecondary(dm, primaryKey);
|
|
91
101
|
if (!secondary || !secondary.isReady()) {
|
|
@@ -94,7 +104,7 @@ export async function sendWithFallback(jid, text, opts = {}) {
|
|
|
94
104
|
}
|
|
95
105
|
try {
|
|
96
106
|
const fallbackRef = await sendVia(secondary, jid, text, opts, drivers.verifyWindowMs, /*skipGuard=*/ true);
|
|
97
|
-
logger.info({ driver: secondary.name, jid, messageId: fallbackRef.id, reason: "primary verification failed" }, "message sent via fallback");
|
|
107
|
+
logger.info({ driver: secondary.name, jid, messageId: fallbackRef.id, reason: primarySendFailed ? "send threw" : "primary verification failed" }, "message sent via fallback");
|
|
98
108
|
return fallbackRef;
|
|
99
109
|
}
|
|
100
110
|
catch (err) {
|
package/dist/locales/en.json
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"connectGaveUp": "Couldn't connect after several attempts. Check your network and try again."
|
|
72
72
|
},
|
|
73
73
|
"whatsmeow": {
|
|
74
|
-
"installPrompt": "Install whatsmeow driver for
|
|
74
|
+
"installPrompt": "Install whatsmeow driver for fallback support? (EXPERIMENTAL — only text send & history work; other methods throw)",
|
|
75
75
|
"unsupportedArch": "whatsmeow driver not available for {{os}}-{{arch}}. The bot will use Baileys only.",
|
|
76
76
|
"fetchingTag": "Fetching latest whatsmeow release...",
|
|
77
77
|
"fetchFailed": "Could not reach Codeberg. Skipping whatsmeow install.",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"downloadFailed": "Download failed: {{reason}}",
|
|
80
80
|
"installTitle": "whatsmeow driver",
|
|
81
81
|
"installed": "whatsmeow driver installed at {{path}}",
|
|
82
|
-
"restartNotice": "Restart the bot for the whatsmeow driver to take effect."
|
|
82
|
+
"restartNotice": "Restart the bot for the whatsmeow driver to take effect.",
|
|
83
|
+
"experimentalNotice": "whatsmeow is EXPERIMENTAL: only sendText and getHistory are implemented. sendImage, sendPoll, groupMetadata, and other methods throw."
|
|
83
84
|
}
|
|
84
85
|
}
|
package/dist/locales/es.json
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"connectGaveUp": "No se pudo conectar tras varios intentos. Revisa tu conexión e intenta de nuevo."
|
|
72
72
|
},
|
|
73
73
|
"whatsmeow": {
|
|
74
|
-
"installPrompt": "¿Instalar el driver whatsmeow para
|
|
74
|
+
"installPrompt": "¿Instalar el driver whatsmeow para soporte de fallback? (EXPERIMENTAL — solo sendText e historial funcionan; otros métodos lanzan error)",
|
|
75
75
|
"unsupportedArch": "Driver whatsmeow no disponible para {{os}}-{{arch}}. El bot usará solo Baileys.",
|
|
76
76
|
"fetchingTag": "Obteniendo última versión de whatsmeow...",
|
|
77
77
|
"fetchFailed": "No se pudo contactar a Codeberg. Instalación de whatsmeow omitida.",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"downloadFailed": "Descarga fallida: {{reason}}",
|
|
80
80
|
"installTitle": "Driver whatsmeow",
|
|
81
81
|
"installed": "Driver whatsmeow instalado en {{path}}",
|
|
82
|
-
"restartNotice": "Reinicia el bot para que el driver whatsmeow surta efecto."
|
|
82
|
+
"restartNotice": "Reinicia el bot para que el driver whatsmeow surta efecto.",
|
|
83
|
+
"experimentalNotice": "whatsmeow es EXPERIMENTAL: solo sendText y getHistory están implementados. sendImage, sendPoll, groupMetadata y otros métodos lanzan error."
|
|
83
84
|
}
|
|
84
85
|
}
|
package/dist/locales/pt.json
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"connectGaveUp": "Não foi possível conectar após várias tentativas. Verifique sua conexão e tente de novo."
|
|
72
72
|
},
|
|
73
73
|
"whatsmeow": {
|
|
74
|
-
"installPrompt": "Instalar driver whatsmeow para
|
|
74
|
+
"installPrompt": "Instalar driver whatsmeow para suporte a fallback? (EXPERIMENTAL — apenas sendText e histórico funcionam; outros métodos lançam erro)",
|
|
75
75
|
"unsupportedArch": "Driver whatsmeow não disponível para {{os}}-{{arch}}. O bot usará apenas Baileys.",
|
|
76
76
|
"fetchingTag": "Buscando última versão do whatsmeow...",
|
|
77
77
|
"fetchFailed": "Não foi possível acessar o Codeberg. Instalação do whatsmeow ignorada.",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"downloadFailed": "Download falhou: {{reason}}",
|
|
80
80
|
"installTitle": "Driver whatsmeow",
|
|
81
81
|
"installed": "Driver whatsmeow instalado em {{path}}",
|
|
82
|
-
"restartNotice": "Reinicie o bot para que o driver whatsmeow entre em efeito."
|
|
82
|
+
"restartNotice": "Reinicie o bot para que o driver whatsmeow entre em efeito.",
|
|
83
|
+
"experimentalNotice": "whatsmeow é EXPERIMENTAL: apenas sendText e getHistory estão implementados. sendImage, sendPoll, groupMetadata e outros métodos lançam erro."
|
|
83
84
|
}
|
|
84
85
|
}
|
package/dist/main.js
CHANGED
|
@@ -11,6 +11,7 @@ process.env.NODE_PATH = path.resolve(process.cwd(), "node_modules");
|
|
|
11
11
|
Module._initPaths();
|
|
12
12
|
import { baileysContract } from "#drivers/baileys/index.js";
|
|
13
13
|
import { whatsmeowContract, startWhatsmeowSupervisor, wrapWithSupervisor } from "#drivers/whatsmeow/index.js";
|
|
14
|
+
import { promptWhatsmeowInstall } from "#drivers/whatsmeow/installer.js";
|
|
14
15
|
import { cleanupPlugins } from "#kernel/pluginLoader.js";
|
|
15
16
|
import { stopAll as stopScheduler } from "#kernel/scheduler.js";
|
|
16
17
|
import { sendAlert } from "#kernel/alerts.js";
|
|
@@ -35,12 +36,20 @@ driverManager.register(baileysContract, { isPrimary: CONFIG.drivers.primary ===
|
|
|
35
36
|
// running on Baileys alone, no fallback.
|
|
36
37
|
let supervisor = null;
|
|
37
38
|
if (CONFIG.drivers.whatsmeow.enabled) {
|
|
39
|
+
logger.info("[driverManager] whatsmeow enabled — spawning supervisor");
|
|
38
40
|
supervisor = await startWhatsmeowSupervisor();
|
|
39
41
|
if (supervisor) {
|
|
40
42
|
const wrapped = wrapWithSupervisor(whatsmeowContract, supervisor);
|
|
41
43
|
driverManager.register(wrapped, { isPrimary: CONFIG.drivers.primary === "whatsmeow" });
|
|
44
|
+
logger.info(`[driverManager] whatsmeow registered (primary=${CONFIG.drivers.primary === "whatsmeow"})`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
logger.warn("[driverManager] whatsmeow supervisor failed to start — fallback disabled");
|
|
42
48
|
}
|
|
43
49
|
}
|
|
50
|
+
else {
|
|
51
|
+
logger.info("[driverManager] whatsmeow disabled by config — no fallback");
|
|
52
|
+
}
|
|
44
53
|
const activeDriver = driverManager.active();
|
|
45
54
|
const secondaryName = (activeDriver.name === "baileys" ? "whatsmeow" : "baileys");
|
|
46
55
|
const secondaryDriver = driverManager.get(secondaryName);
|
|
@@ -125,6 +134,16 @@ if (process.argv.includes("--getid")) {
|
|
|
125
134
|
process.exit(1);
|
|
126
135
|
});
|
|
127
136
|
}
|
|
137
|
+
else if (process.argv.includes("--install-whatsmeow")) {
|
|
138
|
+
// Re-run the whatsmeow installer outside the normal setup flow.
|
|
139
|
+
// Useful when the initial install failed or the binary was moved.
|
|
140
|
+
promptWhatsmeowInstall()
|
|
141
|
+
.then(() => process.exit(0))
|
|
142
|
+
.catch((err) => {
|
|
143
|
+
logger.error(`--install-whatsmeow failed: ${err.message}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
128
147
|
else {
|
|
129
148
|
// Start bot
|
|
130
149
|
logger.info(t("bot.initialized"));
|
|
@@ -143,10 +162,14 @@ else {
|
|
|
143
162
|
// here is non-fatal: the primary keeps running, fallback just stays
|
|
144
163
|
// unavailable (sendFallbackGuard's `isReady()` check covers that).
|
|
145
164
|
if (secondaryDriver) {
|
|
165
|
+
logger.info(`[driverManager] connecting secondary "${secondaryName}" in background…`);
|
|
146
166
|
secondaryDriver.connect()
|
|
147
167
|
.then(() => logger.info(`[driverManager] secondary "${secondaryName}" connected — fallback available`))
|
|
148
168
|
.catch((err) => logger.warn(`[driverManager] secondary "${secondaryName}" connect failed: ${err.message} — fallback unavailable`));
|
|
149
169
|
}
|
|
170
|
+
else {
|
|
171
|
+
logger.info(`[driverManager] no secondary driver registered — running on ${activeDriver.name} only`);
|
|
172
|
+
}
|
|
150
173
|
})
|
|
151
174
|
.catch((err) => {
|
|
152
175
|
shutdown(`Failed to connect driver: ${err.message}`, true);
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "SyntaxError!",
|
|
6
6
|
"email": "me@stxerr.dev"
|
|
7
7
|
},
|
|
8
|
-
"version": "5.
|
|
8
|
+
"version": "5.7.0",
|
|
9
9
|
"license": "GPL-3.0-only",
|
|
10
10
|
"private": false,
|
|
11
11
|
"engines": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@grpc/grpc-js": "^1.14.4",
|
|
41
41
|
"@grpc/proto-loader": "^0.7.15",
|
|
42
42
|
"@hapi/boom": "^10.0.1",
|
|
43
|
-
"@whiskeysockets/baileys": "6.7.
|
|
43
|
+
"@whiskeysockets/baileys": "6.7.24",
|
|
44
44
|
"node-cron": "^4.6.0",
|
|
45
45
|
"node-webpmux": "^3.2.1",
|
|
46
46
|
"nodemailer": "^9.0.3",
|