@manybot/manybot 5.7.0 → 5.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.
- package/README.md +28 -3
- package/dist/client/banner.js +10 -0
- package/dist/client/banner.test.js +31 -0
- package/dist/client/store.js +91 -6
- package/dist/client/store.test.js +170 -0
- package/dist/config.js +28 -44
- package/dist/config.test.js +26 -0
- package/dist/download/queue.js +13 -4
- package/dist/drivers/baileys/adapter.js +133 -15
- package/dist/drivers/baileys/api/contacts.integration.test.js +261 -0
- package/dist/drivers/baileys/api/groupMeta.test.js +235 -0
- package/dist/drivers/baileys/api/index.js +384 -62
- package/dist/drivers/baileys/index.js +92 -36
- package/dist/drivers/baileys/loginPrompt.js +0 -2
- package/dist/drivers/baileys/messageHandler.js +344 -4
- package/dist/drivers/baileys/messageHandler.test.js +445 -0
- package/dist/drivers/baileysAdapter.test.js +378 -0
- package/dist/drivers/jid.js +26 -0
- package/dist/drivers/jid.test.js +74 -0
- package/dist/drivers/types.js +5 -5
- package/dist/i18n/index.js +20 -24
- package/dist/kernel/activeDriverSend.js +21 -0
- package/dist/kernel/activeDriverSend.test.js +89 -0
- package/dist/kernel/alerts.js +3 -9
- package/dist/kernel/chatOverrides.js +46 -0
- package/dist/kernel/chatOverrides.test.js +59 -0
- package/dist/kernel/chatSession.js +65 -0
- package/dist/kernel/chatSession.test.js +46 -0
- package/dist/kernel/commandAccess.js +66 -0
- package/dist/kernel/commandAccess.test.js +74 -0
- package/dist/kernel/commandDeprecation.js +170 -0
- package/dist/kernel/commandDeprecation.test.js +114 -0
- package/dist/kernel/commandMenu.js +357 -0
- package/dist/kernel/commandMenu.test.js +363 -0
- package/dist/kernel/commandPermissions.js +171 -0
- package/dist/kernel/commandPermissions.test.js +227 -0
- package/dist/kernel/commandRegistry.js +583 -0
- package/dist/kernel/commandRegistry.test.js +158 -0
- package/dist/kernel/commandsConfig.js +949 -0
- package/dist/kernel/commandsConfig.test.js +482 -0
- package/dist/kernel/contactAutoSave.js +6 -6
- package/dist/kernel/contactAutoSave.test.js +87 -0
- package/dist/kernel/coreCommands.js +62 -0
- package/dist/kernel/driverManager.js +10 -6
- package/dist/kernel/driverManager.test.js +90 -0
- package/dist/kernel/integrationMode.js +88 -0
- package/dist/kernel/integrationMode.test.js +95 -0
- package/dist/kernel/loadIntegrationPlugin.test.js +67 -0
- package/dist/kernel/pluginApi.test.js +600 -0
- package/dist/kernel/pluginGuard.js +18 -13
- package/dist/kernel/pluginGuard.test.js +39 -0
- package/dist/kernel/pluginLoader.js +169 -11
- package/dist/kernel/pluginLoader.test.js +190 -0
- package/dist/kernel/runCommand.js +284 -0
- package/dist/kernel/runCommand.test.js +497 -0
- package/dist/kernel/sendFallbackGuard.js +19 -48
- package/dist/kernel/sendFallbackGuard.test.js +80 -0
- package/dist/kernel/sendGuard.js +38 -42
- package/dist/kernel/sendGuard.test.js +102 -0
- package/dist/kernel/settingsDb.js +19 -5
- package/dist/kernel/statusServer.js +9 -2
- package/dist/kernel/statusServer.test.js +70 -0
- package/dist/kernel/testConfig.js +192 -0
- package/dist/kernel/testConfig.test.js +181 -0
- package/dist/kernel/updateCheck.js +33 -10
- package/dist/locales/en.json +77 -13
- package/dist/locales/es.json +77 -13
- package/dist/locales/pt.json +77 -13
- package/dist/logger/logger.js +23 -3
- package/dist/logger/logger.test.js +45 -0
- package/dist/main.js +5 -76
- package/dist/plugins/__manybot_integration__/index.js +184 -0
- package/dist/plugins/__manybot_integration__/index.test.js +218 -0
- package/dist/utils/phoneNumber.js +83 -0
- package/dist/utils/phoneNumber.test.js +53 -0
- package/package.json +76 -18
- package/dist/drivers/whatsmeow/client.js +0 -252
- package/dist/drivers/whatsmeow/index.js +0 -79
- package/dist/drivers/whatsmeow/installer.js +0 -86
- package/dist/drivers/whatsmeow/supervisor.js +0 -328
- package/dist/drivers/whatsmeow/whatsmeow.proto +0 -64
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kernel/runCommand.ts
|
|
3
|
+
*
|
|
4
|
+
* ManyBot v6 — single kernel-side dispatcher for routed prefix
|
|
5
|
+
* commands. Centralising this gives Phase 8 a single try/catch hook
|
|
6
|
+
* (the "natural capture point" called out in MANYBOT-6.md) and keeps
|
|
7
|
+
* the per-command logic out of the message handler so each step
|
|
8
|
+
* (permission → required-arguments → sub-command routing → handler
|
|
9
|
+
* dispatch → crash alert) can be tested independently.
|
|
10
|
+
*
|
|
11
|
+
* Pipeline:
|
|
12
|
+
* 1. Resolve the parent entry by invocation (= cmd or alias).
|
|
13
|
+
* 2. Permission check (owner/scope/blacklist/whitelist/admin/botAdmin/cooldown).
|
|
14
|
+
* 3. Sub-command routing: if the parent has subcommands, look up the
|
|
15
|
+
* next token; if a match exists, dispatch to the sub instead.
|
|
16
|
+
* 4. Required-argument validation against the matched entry's
|
|
17
|
+
* declared `arguments:` block.
|
|
18
|
+
* 5. Handler dispatch via `runPlugin` (same timeout + 3-strikes guard
|
|
19
|
+
* as the message handler uses for the legacy plugin.run path).
|
|
20
|
+
* 6. Crash capture: any throw inside step 5 fires `fireAlert` at
|
|
21
|
+
* phase 8 hook level before re-raising; this is the unique
|
|
22
|
+
* advantage of the unified dispatcher over the legacy
|
|
23
|
+
* `for (plugin of pluginRegistry) await runPlugin(...)` loop.
|
|
24
|
+
*/
|
|
25
|
+
import { logger } from "#logger";
|
|
26
|
+
import { tFor } from "#i18n";
|
|
27
|
+
import { CMD_PREFIX } from "#config";
|
|
28
|
+
import { fireAlert } from "./alerts.js";
|
|
29
|
+
import { runPlugin } from "./pluginGuard.js";
|
|
30
|
+
import { checkPermission } from "./commandPermissions.js";
|
|
31
|
+
import { getCommandRegistry } from "./commandRegistry.js";
|
|
32
|
+
import { pluginRegistry, resolvePluginCommandHandler } from "./pluginLoader.js";
|
|
33
|
+
import { STOP_CHAIN } from "./commandsConfig.js";
|
|
34
|
+
import { resolveCoreCommandHandler } from "./coreCommands.js";
|
|
35
|
+
import { getChatLocale, getChatPrefix } from "./chatOverrides.js";
|
|
36
|
+
function flatten(target) {
|
|
37
|
+
if (target.kind === "sub") {
|
|
38
|
+
return {
|
|
39
|
+
kind: "sub",
|
|
40
|
+
entry: target.parent,
|
|
41
|
+
name: `${target.parent.cmd} ${target.sub.cmd}`,
|
|
42
|
+
permissions: target.sub.permissions,
|
|
43
|
+
arguments: target.sub.arguments ?? [],
|
|
44
|
+
args: target.args,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
kind: "parent",
|
|
49
|
+
entry: target.entry,
|
|
50
|
+
name: target.entry.cmd,
|
|
51
|
+
permissions: target.entry.permissions,
|
|
52
|
+
arguments: target.entry.arguments ?? [],
|
|
53
|
+
args: target.args,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Pure resolution — finds the parent + sub (if any) without invoking.
|
|
58
|
+
* `rawArgs` is the body fragment with the leading `<cmd> ` stripped.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveDispatch(command, rawArgs) {
|
|
61
|
+
const registry = getCommandRegistry();
|
|
62
|
+
if (!registry)
|
|
63
|
+
return { target: { kind: "none" } };
|
|
64
|
+
const parentId = registry.byInvocation.get(command);
|
|
65
|
+
if (!parentId)
|
|
66
|
+
return { target: { kind: "none" } };
|
|
67
|
+
const entry = registry.byId.get(parentId);
|
|
68
|
+
if (!entry)
|
|
69
|
+
return { target: { kind: "none" } };
|
|
70
|
+
const trimmed = rawArgs.trim();
|
|
71
|
+
if (Object.keys(entry.subcommands).length === 0 || !trimmed) {
|
|
72
|
+
return { target: { kind: "parent", entry, args: trimmed ? trimmed.split(/\s+/) : [] } };
|
|
73
|
+
}
|
|
74
|
+
const token = trimmed.split(/\s+/)[0].toLowerCase();
|
|
75
|
+
const sub = entry.subcommands[token];
|
|
76
|
+
if (!sub) {
|
|
77
|
+
const remaining = trimmed.split(/\s+/).slice(1);
|
|
78
|
+
return {
|
|
79
|
+
target: { kind: "parent", entry, args: remaining },
|
|
80
|
+
unmatchedSubToken: token,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const rest = trimmed.split(/\s+/).slice(1);
|
|
84
|
+
return { target: { kind: "sub", parent: entry, sub, args: rest } };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Imperative run: takes the dispatcher-ready shapes (plugin, ctx,
|
|
88
|
+
* resolved target) and runs them through the unified pipeline.
|
|
89
|
+
*
|
|
90
|
+
* Returns a small result so the caller (the message handler) can
|
|
91
|
+
* decide whether to send any extra reply, log the action, or fall
|
|
92
|
+
* through to the legacy run loop.
|
|
93
|
+
*/
|
|
94
|
+
export async function runCommand(opts) {
|
|
95
|
+
const { resolution, pluginName, ctx, reply, chatId } = opts;
|
|
96
|
+
const { target } = resolution;
|
|
97
|
+
// Resolved once per dispatch: this chat's `!config` overrides (or the
|
|
98
|
+
// global defaults when none were set) for every kernel-authored reply
|
|
99
|
+
// below (unknown-sub hint, missing-argument usage, ...).
|
|
100
|
+
const lang = chatId ? getChatLocale(chatId) : undefined;
|
|
101
|
+
const prefix = chatId ? getChatPrefix(chatId) : CMD_PREFIX;
|
|
102
|
+
if (target.kind === "none") {
|
|
103
|
+
return { status: "no_dispatch", sentReply: null, suggestedReply: null };
|
|
104
|
+
}
|
|
105
|
+
const flat = flatten(target);
|
|
106
|
+
// Sub-tokens that don't match any declared sub go through the parent
|
|
107
|
+
// handler — same convention as a CLI tool with an unknown subcommand.
|
|
108
|
+
if (resolution.unmatchedSubToken && target.kind === "parent") {
|
|
109
|
+
const validSubs = Object.keys(target.entry.subcommands);
|
|
110
|
+
const help = tFor(lang, "commandRun.unknownSubcommand", {
|
|
111
|
+
sub: resolution.unmatchedSubToken,
|
|
112
|
+
cmd: target.entry.cmd,
|
|
113
|
+
valid: validSubs.join(", ") || "(none)",
|
|
114
|
+
});
|
|
115
|
+
await reply.text(help);
|
|
116
|
+
return { status: "unknown_sub", sentReply: help, suggestedReply: help };
|
|
117
|
+
}
|
|
118
|
+
// Permission check uses the resolved (parent OR sub) permissions.
|
|
119
|
+
const permEntry = target.kind === "sub" ? subAsEntry(target.parent, target.sub) : target.entry;
|
|
120
|
+
const perm = await checkPermission(permEntry, {
|
|
121
|
+
isGroup: ctx.chat.isGroup,
|
|
122
|
+
chatId: ctx.chat.id,
|
|
123
|
+
sender: { lid: ctx.msg.sender, pn: ctx.msg.senderPn },
|
|
124
|
+
isSenderAdmin: () => ctx.chat.isSenderAdmin(),
|
|
125
|
+
isBotAdmin: () => ctx.chat.isBotAdmin(),
|
|
126
|
+
});
|
|
127
|
+
if (!perm.allowed) {
|
|
128
|
+
if (perm.message) {
|
|
129
|
+
await reply.text(perm.message);
|
|
130
|
+
}
|
|
131
|
+
return { status: "permission_denied", sentReply: perm.message ?? null, suggestedReply: perm.message ?? null };
|
|
132
|
+
}
|
|
133
|
+
// Required-argument check — purely advisory, the plugin still gets
|
|
134
|
+
// called with whatever args arrived. Surface the kernel-side message
|
|
135
|
+
// + auto-generated usage before invoking.
|
|
136
|
+
const requiredCount = flat.arguments.filter(a => a.required).length;
|
|
137
|
+
if (requiredCount > flat.args.length) {
|
|
138
|
+
const usage = renderUsage(target, prefix);
|
|
139
|
+
const msg = `${tFor(lang, "commandRun.missingRequiredArg")}\n\n${usage}`;
|
|
140
|
+
await reply.text(msg);
|
|
141
|
+
return { status: "argument_missing", sentReply: msg, suggestedReply: msg };
|
|
142
|
+
}
|
|
143
|
+
// Wrap the dispatch in a try/catch and fire alerts on any throw.
|
|
144
|
+
// This is the Phase 8 hook.
|
|
145
|
+
try {
|
|
146
|
+
if (!pluginName) {
|
|
147
|
+
// text-only command handled below (caller will handle the fixed-text path).
|
|
148
|
+
return { status: "no_dispatch", sentReply: null, suggestedReply: null };
|
|
149
|
+
}
|
|
150
|
+
// Function chain dispatch — v6 reference yaml allows a command to
|
|
151
|
+
// declare `functions: [a, b, c]` and have them run top-to-bottom.
|
|
152
|
+
// Each function receives the same `(ctx, { args, subcommand })`
|
|
153
|
+
// shape; a function may short-circuit the rest of the chain by
|
|
154
|
+
// returning the sentinel `STOP_CHAIN` (exported from
|
|
155
|
+
// `commandsConfig.ts`). Anything else (void/undefined/regular value)
|
|
156
|
+
// lets the chain continue. Empty chain → "no_dispatch" (parent is
|
|
157
|
+
// metadata-only, e.g. a sub-container with no override).
|
|
158
|
+
const fnNames = target.kind === "sub" ? target.sub.functions : target.entry.functions;
|
|
159
|
+
if (fnNames.length === 0) {
|
|
160
|
+
return { status: "no_dispatch", sentReply: null, suggestedReply: null };
|
|
161
|
+
}
|
|
162
|
+
const subId = target.kind === "sub" ? target.sub.cmd : undefined;
|
|
163
|
+
const input = { args: flat.args, subcommand: subId };
|
|
164
|
+
if (pluginName === "core") {
|
|
165
|
+
for (const fnName of fnNames) {
|
|
166
|
+
const handler = resolveCoreCommandHandler(fnName);
|
|
167
|
+
if (!handler) {
|
|
168
|
+
logger.warn(`[runCommand] Core namespace does not expose handler "${fnName}"`);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const result = await handler(ctx, input);
|
|
172
|
+
if (result === STOP_CHAIN)
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
return { status: "executed", sentReply: null, suggestedReply: null };
|
|
176
|
+
}
|
|
177
|
+
const plugin = lookupPlugin(pluginName);
|
|
178
|
+
if (!plugin) {
|
|
179
|
+
const msg = `Plugin "${pluginName}" is not active`;
|
|
180
|
+
logger.warn(`[runCommand] ${msg}`);
|
|
181
|
+
return { status: "no_dispatch", sentReply: null, suggestedReply: msg };
|
|
182
|
+
}
|
|
183
|
+
for (const fnName of fnNames) {
|
|
184
|
+
const handler = resolvePluginCommandHandler(plugin.commands?.[fnName]);
|
|
185
|
+
if (!handler) {
|
|
186
|
+
const msg = `Plugin "${pluginName}" does not expose handler for !${flat.name}.${fnName}`;
|
|
187
|
+
logger.warn(`[runCommand] ${msg}`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const result = await runPlugin(plugin, ctx, handler, input, { rethrow: true });
|
|
191
|
+
if (result === STOP_CHAIN)
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
return { status: "executed", sentReply: null, suggestedReply: null };
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
198
|
+
fireAlert("plugin_crash", {
|
|
199
|
+
plugin: pluginName,
|
|
200
|
+
command: flat.name,
|
|
201
|
+
kind: err.message?.startsWith("timed out") ? "timeout" : "exception",
|
|
202
|
+
message: err.message,
|
|
203
|
+
});
|
|
204
|
+
// Re-raise so pluginGuard can keep its 3-strike bookkeeping.
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Treat a sub-command as if it were a CommandEntry for permission
|
|
210
|
+
* checks. We materialise a virtual entry sharing the sub's resolved
|
|
211
|
+
* permissions, since `checkPermission` works against `CommandEntry`.
|
|
212
|
+
*/
|
|
213
|
+
function subAsEntry(parent, sub) {
|
|
214
|
+
return {
|
|
215
|
+
...parent,
|
|
216
|
+
id: sub.id,
|
|
217
|
+
cmd: sub.cmd,
|
|
218
|
+
permissions: sub.permissions,
|
|
219
|
+
subcommands: {},
|
|
220
|
+
arguments: sub.arguments,
|
|
221
|
+
group: null,
|
|
222
|
+
manual: sub.manual,
|
|
223
|
+
categoryHiddenInScope: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Look up the real, live `PluginEntry` for a plugin command dispatch.
|
|
228
|
+
* `pluginRegistry` (from `pluginLoader.ts`) is the same module-level
|
|
229
|
+
* map `commandRegistry.ts` reads at build time — its `commands` field
|
|
230
|
+
* carries the plugin's raw `commands` export as-is (bare handler
|
|
231
|
+
* function or `{ handler, ... }` object); `resolvePluginCommandHandler`
|
|
232
|
+
* normalizes either shape into a callable handler.
|
|
233
|
+
*
|
|
234
|
+
* Returning the *real* entry (not a throwaway `{ name }` stand-in)
|
|
235
|
+
* matters: `runPlugin` (`pluginGuard.ts`) gates on `plugin.status ===
|
|
236
|
+
* "active"` and persists `errorCount`/3-strikes bookkeeping onto the
|
|
237
|
+
* object it's given — a fresh literal each call would silently no-op
|
|
238
|
+
* every dispatch (fails the `status` check) and never accumulate
|
|
239
|
+
* crash counts.
|
|
240
|
+
*
|
|
241
|
+
* NOTE: this is deliberately NOT `ctx.plugins.require(pluginName)` —
|
|
242
|
+
* that facet returns `PluginEntry.exports` (the plugin's pure `api`
|
|
243
|
+
* object, Phase 3's `export const api = {...}`, which never receives
|
|
244
|
+
* `ctx`). Command/sub-command handlers are a different facet
|
|
245
|
+
* (`PluginEntry.commands`) and always receive `ctx`.
|
|
246
|
+
*/
|
|
247
|
+
function lookupPlugin(pluginName) {
|
|
248
|
+
const plugin = pluginRegistry.get(pluginName);
|
|
249
|
+
if (!plugin || plugin.status !== "active" || !plugin.commands)
|
|
250
|
+
return null;
|
|
251
|
+
return plugin;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Render an auto-generated usage line for a command/sub. Format:
|
|
255
|
+
* !<cmd>[ <sub>] [--<arg1> ...]
|
|
256
|
+
* Built from the declared `arguments:` block.
|
|
257
|
+
*/
|
|
258
|
+
export function renderUsage(target, prefix = CMD_PREFIX) {
|
|
259
|
+
if (target.kind === "none")
|
|
260
|
+
return "";
|
|
261
|
+
const flat = flatten(target);
|
|
262
|
+
const head = target.kind === "sub"
|
|
263
|
+
? `${prefix}${target.parent.cmd} ${target.sub.cmd}`
|
|
264
|
+
: `${prefix}${flat.entry.cmd}`;
|
|
265
|
+
const headClean = head.replace(/\s+$/, "");
|
|
266
|
+
if (flat.arguments.length === 0)
|
|
267
|
+
return headClean;
|
|
268
|
+
const parts = [];
|
|
269
|
+
for (const arg of flat.arguments) {
|
|
270
|
+
const tok = arg.type === "boolean" ? `--${arg.name}[=true|false]` :
|
|
271
|
+
arg.type === "choice" ? `--${arg.name}=<${arg.choices?.join("|") ?? "..."}>` :
|
|
272
|
+
arg.type === "mention" ? `@<user>` :
|
|
273
|
+
arg.type === "url" ? `<url>` :
|
|
274
|
+
arg.type === "media_direct" ? `<media>` :
|
|
275
|
+
arg.type === "media_reply" ? `<reply-media>` :
|
|
276
|
+
arg.type === "number" ? `<n>` :
|
|
277
|
+
arg.type === "duration" ? `<duration>` :
|
|
278
|
+
arg.type === "quoted_text" ? `"<text>"` :
|
|
279
|
+
arg.type === "reply" ? `<reply>` :
|
|
280
|
+
`<${arg.name}>`;
|
|
281
|
+
parts.push(arg.required ? tok : `[${tok}]`);
|
|
282
|
+
}
|
|
283
|
+
return `${headClean} ${parts.join(" ")}`;
|
|
284
|
+
}
|