@manybot/manybot 5.1.0 → 5.2.1
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 +5 -7
- package/dist/client/banner.js +35 -0
- package/dist/client/store.js +135 -0
- package/dist/config.js +363 -0
- package/dist/core/adapter.js +12 -0
- package/dist/core/capabilities.js +16 -0
- package/dist/core/types.js +6 -0
- package/dist/download/queue.js +49 -0
- package/dist/drivers/index.js +14 -0
- package/dist/drivers/patches/index.js +9 -0
- package/dist/drivers/patches/libsignal.js +16 -0
- package/dist/drivers/patches/patch.js +1 -0
- package/dist/drivers/whatsapp/adapter.js +7 -0
- package/dist/drivers/whatsapp/api/index.js +1311 -0
- package/dist/drivers/whatsapp/index.js +164 -0
- package/dist/drivers/whatsapp/loginPrompt.js +81 -0
- package/dist/drivers/whatsapp/messageHandler.js +111 -0
- package/dist/drivers/whatsapp/sdk/baileysSock.js +124 -0
- package/dist/i18n/index.js +202 -0
- package/dist/kernel/pluginApi.js +11 -0
- package/dist/kernel/pluginGuard.js +88 -0
- package/dist/kernel/pluginLoader.js +322 -0
- package/dist/kernel/scheduler.js +110 -0
- package/dist/kernel/sendGuard.js +121 -0
- package/dist/kernel/settingsDb.js +205 -0
- package/{src → dist}/locales/en.json +18 -33
- package/dist/locales/es.json +52 -0
- package/{src → dist}/locales/pt.json +18 -34
- package/dist/logger/logger.js +16 -0
- package/dist/main.js +82 -0
- package/dist/types.js +16 -0
- package/{src → dist}/utils/file.js +3 -3
- package/package.json +35 -26
- package/src/client/banner.js +0 -57
- package/src/client/whatsappClient.js +0 -185
- package/src/config.js +0 -288
- package/src/download/queue.js +0 -55
- package/src/i18n/index.js +0 -235
- package/src/kernel/messageHandler.js +0 -88
- package/src/kernel/pluginApi.js +0 -630
- package/src/kernel/pluginGuard.js +0 -78
- package/src/kernel/pluginLoader.js +0 -177
- package/src/kernel/pluginState.js +0 -99
- package/src/kernel/scheduler.js +0 -48
- package/src/kernel/sendGuard.js +0 -166
- package/src/locales/es.json +0 -62
- package/src/logger/logger.js +0 -32
- package/src/main.js +0 -136
- package/src/utils/getChatId.js +0 -3
- package/src/utils/get_id.js +0 -177
- package/src/utils/pluginI18n.js +0 -129
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pluginLoader.js
|
|
3
|
-
*
|
|
4
|
-
* Responsible for:
|
|
5
|
-
* 1. Reading active plugins (config.js imports this module and give the list)
|
|
6
|
-
* 2. Loading each plugin from ~/.manybot/plugins folder
|
|
7
|
-
* 3. Registering in pluginRegistry with status and public exports
|
|
8
|
-
* 4. Exposing pluginRegistry to kernel and pluginApi
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import path from "path";
|
|
14
|
-
import { logger } from "#logger";
|
|
15
|
-
import { t } from "#i18n";
|
|
16
|
-
import { pathToFileURL } from "url";
|
|
17
|
-
import { PATHS } from "#config";
|
|
18
|
-
import { buildSetupApi } from "#manyapi";
|
|
19
|
-
|
|
20
|
-
const PLUGINS_DIR = path.join(PATHS.HOME, "plugins");
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Each entry in registry:
|
|
24
|
-
* {
|
|
25
|
-
* name: string,
|
|
26
|
-
* status: "active" | "disabled" | "error",
|
|
27
|
-
* run: async function({ msg, chat, api }) — plugin default function
|
|
28
|
-
* exports: any — what plugin exposed via `export const api = { ... }`
|
|
29
|
-
* error: Error | null
|
|
30
|
-
* }
|
|
31
|
-
*
|
|
32
|
-
* @type {Map<string, object>}
|
|
33
|
-
*/
|
|
34
|
-
export const pluginRegistry = new Map();
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Load all active plugins listed in `activePlugins`.
|
|
38
|
-
* Called once during bot initialization.
|
|
39
|
-
*
|
|
40
|
-
* @param {string[]} activePlugins — active plugin names (from .conf)
|
|
41
|
-
*/
|
|
42
|
-
export async function loadPlugins(activePlugins) {
|
|
43
|
-
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
44
|
-
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
for (const name of activePlugins) {
|
|
48
|
-
await loadPlugin(name);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const total = pluginRegistry.size;
|
|
52
|
-
const ativos = [...pluginRegistry.values()].filter(p => p.status === "active").length;
|
|
53
|
-
const erros = total - ativos;
|
|
54
|
-
|
|
55
|
-
logger.success(t("system.pluginsLoaded", {
|
|
56
|
-
count: ativos,
|
|
57
|
-
errors: erros ? t("system.pluginsLoadedWithErrors", { count: erros }) : ""
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Call setup(api) on all plugins that export it.
|
|
63
|
-
* Executed once after bot connects to WhatsApp.
|
|
64
|
-
*
|
|
65
|
-
* @param {object} api — api without message context (only sendTo, log, schedule...)
|
|
66
|
-
*/
|
|
67
|
-
export async function setupPlugins(client) {
|
|
68
|
-
for (const plugin of pluginRegistry.values()) {
|
|
69
|
-
if (plugin.status !== "active" || !plugin.setup)
|
|
70
|
-
continue;
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const api = buildSetupApi(
|
|
74
|
-
client,
|
|
75
|
-
pluginRegistry,
|
|
76
|
-
plugin.name
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
await plugin.setup(api);
|
|
80
|
-
|
|
81
|
-
} catch (err) {
|
|
82
|
-
logger.error(
|
|
83
|
-
t("system.pluginSetupFailed", {
|
|
84
|
-
name: plugin.name,
|
|
85
|
-
message: err.message
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function findPluginPath(name) {
|
|
93
|
-
const dir = path.join(PLUGINS_DIR, name);
|
|
94
|
-
const manifest = path.join(dir, "manyplug.json");
|
|
95
|
-
|
|
96
|
-
if (!fs.existsSync(manifest))
|
|
97
|
-
return null;
|
|
98
|
-
|
|
99
|
-
const { main = "index.js" } =
|
|
100
|
-
JSON.parse(
|
|
101
|
-
await fs.promises.readFile(
|
|
102
|
-
manifest,
|
|
103
|
-
"utf8"
|
|
104
|
-
)
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const entry =
|
|
108
|
-
path.join(
|
|
109
|
-
dir,
|
|
110
|
-
main
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
fs.existsSync(entry)
|
|
115
|
-
? entry
|
|
116
|
-
: null
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Carrega um único plugin pelo nome.
|
|
122
|
-
* @param {string} name
|
|
123
|
-
*/
|
|
124
|
-
async function loadPlugin(name) {
|
|
125
|
-
const pluginPath = await findPluginPath(name);
|
|
126
|
-
if (!pluginPath) {
|
|
127
|
-
logger.warn(t("system.pluginNotFound", { name, path: path.join(PLUGINS_DIR, name) }));
|
|
128
|
-
pluginRegistry.set(name, { name, status: "disabled", run: null, exports: null, error: null });
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!fs.existsSync(pluginPath)) {
|
|
133
|
-
logger.warn(t("system.pluginNotFound", { name, path: pluginPath }));
|
|
134
|
-
pluginRegistry.set(name, { name, status: "disabled", run: null, exports: null, error: null });
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
const mod = await import(pathToFileURL(pluginPath).href);
|
|
140
|
-
|
|
141
|
-
// Plugin must export a default function — this is called on every message
|
|
142
|
-
if (typeof mod.default !== "function") {
|
|
143
|
-
throw new Error(`Plugin "${name}" does not export a default function`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
pluginRegistry.set(name, {
|
|
147
|
-
name,
|
|
148
|
-
status: "active",
|
|
149
|
-
run: mod.default,
|
|
150
|
-
setup: mod.setup ?? null,
|
|
151
|
-
exports: mod.api ?? null,
|
|
152
|
-
error: null,
|
|
153
|
-
guardOptions: mod.guardOptions ?? {},
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
logger.info(t("system.pluginLoaded", { name }));
|
|
157
|
-
} catch (err) {
|
|
158
|
-
logger.error(t("system.pluginLoadFailed", { name, message: err.message }));
|
|
159
|
-
pluginRegistry.set(name, { name, status: "error", run: null, exports: null, error: err });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export async function cleanupPlugins() {
|
|
164
|
-
for (const plugin of pluginRegistry.values()) {
|
|
165
|
-
try {
|
|
166
|
-
await plugin.exports?.events?.cleanup?.();
|
|
167
|
-
|
|
168
|
-
} catch (err) {
|
|
169
|
-
logger.error(
|
|
170
|
-
t("system.pluginCleanupFailed", {
|
|
171
|
-
name: plugin.name,
|
|
172
|
-
message: err.message
|
|
173
|
-
})
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pluginState.js
|
|
3
|
-
*
|
|
4
|
-
* Tracks plugin execution state per chat.
|
|
5
|
-
* Used to implement the service vs non-service behavior:
|
|
6
|
-
* - Services (service: true) can run regardless of state
|
|
7
|
-
* - Non-services are blocked when another plugin is running in the same chat
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { logger } from "#logger";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Map<chatId, { pluginName: string, startedAt: Date }>
|
|
14
|
-
* Tracks which plugin is currently "holding the lock" in each chat
|
|
15
|
-
*/
|
|
16
|
-
const runningPlugins = new Map();
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Check if any plugin is currently running in a specific chat
|
|
20
|
-
* @param {string} chatId - Chat ID (serialized)
|
|
21
|
-
* @returns {boolean}
|
|
22
|
-
*/
|
|
23
|
-
export function isPluginRunning(chatId) {
|
|
24
|
-
return runningPlugins.has(chatId);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Get info about the plugin running in a chat
|
|
29
|
-
* @param {string} chatId - Chat ID (serialized)
|
|
30
|
-
* @returns {{ pluginName: string, startedAt: Date } | null}
|
|
31
|
-
*/
|
|
32
|
-
export function getRunningPlugin(chatId) {
|
|
33
|
-
return runningPlugins.get(chatId) ?? null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Mark a plugin as running in a chat
|
|
38
|
-
* @param {string} chatId - Chat ID (serialized)
|
|
39
|
-
* @param {string} pluginName - Name of the plugin taking the lock
|
|
40
|
-
*/
|
|
41
|
-
export function startPluginRun(chatId, pluginName) {
|
|
42
|
-
runningPlugins.set(chatId, {
|
|
43
|
-
pluginName,
|
|
44
|
-
startedAt: new Date()
|
|
45
|
-
});
|
|
46
|
-
logger.debug(`Plugin "${pluginName}" started in chat ${chatId}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Mark a plugin as finished in a chat
|
|
51
|
-
* @param {string} chatId - Chat ID (serialized)
|
|
52
|
-
* @param {string} pluginName - Name of the plugin releasing the lock
|
|
53
|
-
*/
|
|
54
|
-
export function endPluginRun(chatId, pluginName) {
|
|
55
|
-
const current = runningPlugins.get(chatId);
|
|
56
|
-
if (current && current.pluginName === pluginName) {
|
|
57
|
-
runningPlugins.delete(chatId);
|
|
58
|
-
logger.debug(`Plugin "${pluginName}" ended in chat ${chatId}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Force clear the running state for a chat
|
|
64
|
-
* Useful for cleanup or admin commands
|
|
65
|
-
* @param {string} chatId - Chat ID (serialized)
|
|
66
|
-
*/
|
|
67
|
-
export function clearPluginRun(chatId) {
|
|
68
|
-
runningPlugins.delete(chatId);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Get all chats where a specific plugin is running
|
|
73
|
-
* @param {string} pluginName - Plugin name
|
|
74
|
-
* @returns {string[]} Array of chat IDs
|
|
75
|
-
*/
|
|
76
|
-
export function getChatsWithPlugin(pluginName) {
|
|
77
|
-
const chats = [];
|
|
78
|
-
for (const [chatId, info] of runningPlugins.entries()) {
|
|
79
|
-
if (info.pluginName === pluginName) {
|
|
80
|
-
chats.push(chatId);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return chats;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Get stats about running plugins
|
|
88
|
-
* @returns {{ total: number, byPlugin: Record<string, number> }}
|
|
89
|
-
*/
|
|
90
|
-
export function getStats() {
|
|
91
|
-
const byPlugin = {};
|
|
92
|
-
for (const info of runningPlugins.values()) {
|
|
93
|
-
byPlugin[info.pluginName] = (byPlugin[info.pluginName] || 0) + 1;
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
total: runningPlugins.size,
|
|
97
|
-
byPlugin
|
|
98
|
-
};
|
|
99
|
-
}
|
package/src/kernel/scheduler.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scheduler.js
|
|
3
|
-
*
|
|
4
|
-
* Allows plugins to register scheduled tasks via cron.
|
|
5
|
-
* Uses node-cron underneath, but plugins never import node-cron directly —
|
|
6
|
-
* they only call api.schedule(cron, fn).
|
|
7
|
-
*
|
|
8
|
-
* Usage in plugin:
|
|
9
|
-
* import { schedule } from "many";
|
|
10
|
-
* schedule("0 9 * * 1", async () => { await api.send("Good morning!"); });
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import cron from "node-cron";
|
|
14
|
-
import { logger } from "#logger";
|
|
15
|
-
import { t } from "#i18n";
|
|
16
|
-
|
|
17
|
-
/** List of active tasks (for eventual teardown) */
|
|
18
|
-
const tasks = [];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Register a cron task.
|
|
22
|
-
* @param {string} expression — cron expression e.g., "0 9 * * 1"
|
|
23
|
-
* @param {Function} fn — async function to execute
|
|
24
|
-
* @param {string} pluginName — plugin name (for logging)
|
|
25
|
-
*/
|
|
26
|
-
export function schedule(expression, fn, pluginName = "unknown") {
|
|
27
|
-
if (!cron.validate(expression)) {
|
|
28
|
-
logger.warn(t("system.schedulerInvalidCron", { name: pluginName, expression }));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const task = cron.schedule(expression, async () => {
|
|
33
|
-
try {
|
|
34
|
-
await fn();
|
|
35
|
-
} catch (err) {
|
|
36
|
-
logger.error(t("system.schedulerError", { name: pluginName, message: err.message }));
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
tasks.push({ pluginName, expression, task });
|
|
41
|
-
logger.info(t("system.schedulerRegistered", { name: pluginName, expression }));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Stop all schedules (useful for shutdown) */
|
|
45
|
-
export function stopAll() {
|
|
46
|
-
tasks.forEach(({ task }) => task.stop());
|
|
47
|
-
tasks.length = 0;
|
|
48
|
-
}
|
package/src/kernel/sendGuard.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sendGuard.js
|
|
3
|
-
*
|
|
4
|
-
* Anti-detection throttle layer for all outbound sends.
|
|
5
|
-
*
|
|
6
|
-
* Three protections applied before every message:
|
|
7
|
-
* 1. Global token bucket — hard cap on messages/second across all chats
|
|
8
|
-
* 2. Per-chat cooldown — minimum gap between sends to the same chat
|
|
9
|
-
* 3. Human jitter — random delay to break robotic timing patterns
|
|
10
|
-
*
|
|
11
|
-
* Text sends also simulate the typing indicator so the chat shows
|
|
12
|
-
* "typing…" for a realistic duration before the message arrives.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { logger } from "#logger";
|
|
16
|
-
|
|
17
|
-
// ── Tunables ──────────────────────────────────────────────────────────────────
|
|
18
|
-
// Adjust conservatively — WhatsApp is more sensitive to burst than average rate.
|
|
19
|
-
|
|
20
|
-
/** Hard cap: max messages per second, globally (across all chats). */
|
|
21
|
-
const GLOBAL_MSG_PER_SEC = 3;
|
|
22
|
-
|
|
23
|
-
/** Minimum ms between two sends to the same chat. */
|
|
24
|
-
const CHAT_COOLDOWN_MS = 900;
|
|
25
|
-
|
|
26
|
-
/** Random jitter window added before every send (ms). */
|
|
27
|
-
const JITTER_MS = { min: 400, max: 1400 };
|
|
28
|
-
|
|
29
|
-
/** Typing speed used to calculate indicator duration (chars/sec). */
|
|
30
|
-
const TYPING_CPS = 55;
|
|
31
|
-
|
|
32
|
-
/** Upper cap on typing simulation, regardless of message length. */
|
|
33
|
-
const TYPING_MAX_MS = 4500;
|
|
34
|
-
|
|
35
|
-
/** Fixed indicator duration for media sends (ms), before jitter. */
|
|
36
|
-
const MEDIA_INDICATOR_MS = { min: 800, max: 2000 };
|
|
37
|
-
|
|
38
|
-
// ── Global token bucket ───────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
const MS_PER_TOKEN = 1000 / GLOBAL_MSG_PER_SEC;
|
|
41
|
-
|
|
42
|
-
let tokens = GLOBAL_MSG_PER_SEC;
|
|
43
|
-
let lastRefill = Date.now();
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Consume one send token.
|
|
47
|
-
* Returns the number of ms to wait if no token is available (0 = proceed now).
|
|
48
|
-
*/
|
|
49
|
-
function consumeGlobalToken() {
|
|
50
|
-
const now = Date.now();
|
|
51
|
-
const elapsed = now - lastRefill;
|
|
52
|
-
|
|
53
|
-
tokens = Math.min(GLOBAL_MSG_PER_SEC, tokens + elapsed / MS_PER_TOKEN);
|
|
54
|
-
lastRefill = now;
|
|
55
|
-
|
|
56
|
-
if (tokens >= 1) {
|
|
57
|
-
tokens -= 1;
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return Math.ceil((1 - tokens) * MS_PER_TOKEN);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ── Per-chat cooldown ─────────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
/** chatId → timestamp of the last outbound send */
|
|
67
|
-
const lastSentAt = new Map();
|
|
68
|
-
|
|
69
|
-
function chatCooldownMs(chatId) {
|
|
70
|
-
const last = lastSentAt.get(chatId) ?? 0;
|
|
71
|
-
const wait = last + CHAT_COOLDOWN_MS - Date.now();
|
|
72
|
-
return wait > 0 ? wait : 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function recordSend(chatId) {
|
|
76
|
-
lastSentAt.set(chatId, Date.now());
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
82
|
-
|
|
83
|
-
function randomJitter() {
|
|
84
|
-
return JITTER_MS.min + Math.random() * (JITTER_MS.max - JITTER_MS.min);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* How long the typing indicator should appear before sending text.
|
|
90
|
-
* Based on simulated typing speed, capped at TYPING_MAX_MS.
|
|
91
|
-
* @param {string} text
|
|
92
|
-
* @returns {number} ms
|
|
93
|
-
*/
|
|
94
|
-
export function typingDuration(text) {
|
|
95
|
-
if (typeof text !== "string" || text.length === 0) return 0;
|
|
96
|
-
return Math.min((text.length / TYPING_CPS) * 1000, TYPING_MAX_MS);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* A human-feeling duration for media "processing" indicator.
|
|
101
|
-
* Randomized so repeated sends don't have identical pauses.
|
|
102
|
-
* @returns {number} ms
|
|
103
|
-
*/
|
|
104
|
-
export function mediaDuration() {
|
|
105
|
-
return MEDIA_INDICATOR_MS.min
|
|
106
|
-
+ Math.random() * (MEDIA_INDICATOR_MS.max - MEDIA_INDICATOR_MS.min);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ── Public API ────────────────────────────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Wait for a safe send slot: global rate → per-chat cooldown → jitter.
|
|
113
|
-
* Must be called before every outbound message.
|
|
114
|
-
*
|
|
115
|
-
* @param {string} chatId
|
|
116
|
-
* @param {object} [opts]
|
|
117
|
-
* @param {boolean} [opts.cooldown=true] — set to `false` to skip per-chat cooldown.
|
|
118
|
-
* Use for plugins that reply instantly and
|
|
119
|
-
* don't benefit from anti-detection pacing
|
|
120
|
-
* (e.g. sticker). Global rate limit is kept.
|
|
121
|
-
* @param {boolean} [opts.jitter=true] — set to `false` to skip random jitter delay.
|
|
122
|
-
*/
|
|
123
|
-
export async function waitForSendSlot(chatId, { cooldown = true, jitter = true } = {}) {
|
|
124
|
-
const tokenWait = consumeGlobalToken();
|
|
125
|
-
if (tokenWait > 0) {
|
|
126
|
-
logger.debug(`[sendGuard] global rate hit — queuing ${tokenWait}ms`);
|
|
127
|
-
await sleep(tokenWait);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (cooldown) {
|
|
131
|
-
const coolWait = chatCooldownMs(chatId);
|
|
132
|
-
if (coolWait > 0) {
|
|
133
|
-
logger.debug(`[sendGuard] chat cooldown (${chatId}) — waiting ${coolWait}ms`);
|
|
134
|
-
await sleep(coolWait);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (jitter) {
|
|
139
|
-
await sleep(randomJitter());
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
recordSend(chatId);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Show a WhatsApp presence indicator for `ms` milliseconds, then clear it.
|
|
147
|
-
* Best-effort — errors are swallowed so they never break a send.
|
|
148
|
-
*
|
|
149
|
-
* @param {import("whatsapp-web.js").Chat} chat
|
|
150
|
-
* @param {number} ms
|
|
151
|
-
* @param {"typing"|"recording"} [state="typing"]
|
|
152
|
-
*/
|
|
153
|
-
export async function simulateState(chat, ms, state = "typing") {
|
|
154
|
-
if (!chat || ms <= 0) return;
|
|
155
|
-
try {
|
|
156
|
-
if (state === "recording") {
|
|
157
|
-
await chat.sendStateRecording();
|
|
158
|
-
} else {
|
|
159
|
-
await chat.sendStateTyping();
|
|
160
|
-
}
|
|
161
|
-
await sleep(ms);
|
|
162
|
-
await chat.clearState();
|
|
163
|
-
} catch (err) {
|
|
164
|
-
logger.debug(`[sendGuard] state simulation failed (non-fatal): ${err.message}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
package/src/locales/es.json
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"bot": {
|
|
3
|
-
"starting": "Iniciando ManyBot...",
|
|
4
|
-
"initialized": "Cliente inicializado. Esperando conexión con WhatsApp...",
|
|
5
|
-
"ready": "¡Bot está listo!",
|
|
6
|
-
"error": {
|
|
7
|
-
"uncaught": "Excepción no capturada",
|
|
8
|
-
"unhandled": "Rechazo no manejado"
|
|
9
|
-
},
|
|
10
|
-
"signal": {
|
|
11
|
-
"sigterm": "Proceso interrumpida. Apagando..."
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"log": {
|
|
15
|
-
"info": "INFO",
|
|
16
|
-
"success": "OK",
|
|
17
|
-
"warn": "WARN",
|
|
18
|
-
"error": "ERROR",
|
|
19
|
-
"msg": "MSG",
|
|
20
|
-
"cmd": "CMD",
|
|
21
|
-
"done": "DONE",
|
|
22
|
-
"context": {
|
|
23
|
-
"group": "grupo",
|
|
24
|
-
"from": "De",
|
|
25
|
-
"type": "Tipo",
|
|
26
|
-
"replyTo": "Responde a"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
"system": {
|
|
30
|
-
"environment": "Entorno: {{platform}} — usando {{puppeteer}}",
|
|
31
|
-
"environmentTermux": "Entorno: Termux — usando Chromium del sistema",
|
|
32
|
-
"connected": "¡WhatsApp conectado y listo!",
|
|
33
|
-
"disconnected": "Desconectado — motivo: {{reason}}",
|
|
34
|
-
"reconnecting": "Reconectando en {{seconds}}s...",
|
|
35
|
-
"reinitializing": "Reinicializando cliente...",
|
|
36
|
-
"qrSaved": "Código QR guardado en: {{path}}",
|
|
37
|
-
"qrOpen": "Abrir con: termux-open qr.png",
|
|
38
|
-
"qrSaveFailed": "Error al guardar el Código QR:",
|
|
39
|
-
"qrScan": "Escanea el Código QR abajo:",
|
|
40
|
-
"clientId": "Client ID: {{id}}",
|
|
41
|
-
"pluginsFolderNotFound": "Carpeta de plugins no encontrada. Ningún plugin cargado.",
|
|
42
|
-
"pluginsLoaded": "Plugins cargados: {{count}} activos{{errors}}",
|
|
43
|
-
"pluginsLoadedWithErrors": ", {{count}} con error",
|
|
44
|
-
"pluginSetupFailed": "Error en la configuración del plugin \"{{name}}\": {{message}}",
|
|
45
|
-
"pluginNotFound": "Plugin \"{{name}}\" no encontrado en {{path}}",
|
|
46
|
-
"pluginLoaded": "Plugin cargado: {{name}}",
|
|
47
|
-
"pluginLoadFailed": "Error al cargar el plugin \"{{name}}\": {{message}}",
|
|
48
|
-
"pluginDisabledAfterError": "Plugin \"{{name}}\" desactivado después del error: {{message}}",
|
|
49
|
-
"schedulerInvalidCron": "Plugin \"{{name}}\" registró expresión cron inválida: \"{{expression}}\"",
|
|
50
|
-
"schedulerError": "Error en la programación del plugin \"{{name}}\": {{message}}",
|
|
51
|
-
"schedulerRegistered": "Programación registrada — plugin \"{{name}}\" → \"{{expression}}\"",
|
|
52
|
-
"downloadJobFailed": "Error en el trabajo de descarga — {{message}}"
|
|
53
|
-
},
|
|
54
|
-
"errors": {
|
|
55
|
-
"pluginLoad": "Error al cargar el plugin",
|
|
56
|
-
"messageProcess": "Error al procesar el mensaje",
|
|
57
|
-
"stack": "Stack",
|
|
58
|
-
"chromeNotFound": "No se ha encontrado Chrome. Descargando...",
|
|
59
|
-
"couldNotDownloadChrome": "No se ha podido descargar Chrome: ",
|
|
60
|
-
"OSNotSupported": "Sistema operativo no compatible: {{os}}. Si tienes alguna duda, consulta: https://manybot.stxerr.dev/docs/getting-started/"
|
|
61
|
-
}
|
|
62
|
-
}
|
package/src/logger/logger.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const c = {
|
|
2
|
-
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
3
|
-
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
|
4
|
-
red: "\x1b[31m", gray: "\x1b[90m", white: "\x1b[37m",
|
|
5
|
-
blue: "\x1b[34m", magenta: "\x1b[35m",
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* ManyBot central logger.
|
|
10
|
-
* Each method only handles output — no business logic or external I/O.
|
|
11
|
-
*/
|
|
12
|
-
export const logger = {
|
|
13
|
-
info: (...a) => console.log(`${c.cyan }INFO ${c.reset}`, ...a),
|
|
14
|
-
success: (...a) => console.log(`${c.green }OK ${c.reset}`, ...a),
|
|
15
|
-
warn: (...a) => console.log(`${c.yellow}WARN ${c.reset}`, ...a),
|
|
16
|
-
error: (...a) => console.log(`${c.red }ERROR ${c.reset}`, ...a),
|
|
17
|
-
debug: (...a) => console.log(`${c.blue }DEBUG ${c.reset}`, ...a),
|
|
18
|
-
|
|
19
|
-
cmd: (cmd, extra = "") =>
|
|
20
|
-
console.log(
|
|
21
|
-
`${c.gray}${now()}${c.reset}${c.yellow}CMD ${c.reset}` +
|
|
22
|
-
`${c.bold}${cmd}${c.reset}` +
|
|
23
|
-
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
|
|
24
|
-
),
|
|
25
|
-
|
|
26
|
-
done: (cmd, detail = "") =>
|
|
27
|
-
console.log(
|
|
28
|
-
`${c.gray}${now()}${c.reset}${c.green}DONE ${c.reset}` +
|
|
29
|
-
`${c.dim}${cmd}${c.reset}` +
|
|
30
|
-
(detail ? ` — ${detail}` : "")
|
|
31
|
-
),
|
|
32
|
-
};
|