@manybot/manybot 4.0.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/LICENSE +674 -0
- package/README.md +18 -0
- package/package.json +53 -0
- package/src/client/banner.js +57 -0
- package/src/client/whatsappClient.js +103 -0
- package/src/config.js +196 -0
- package/src/download/queue.js +55 -0
- package/src/i18n/index.js +235 -0
- package/src/kernel/messageHandler.js +39 -0
- package/src/kernel/pluginApi.js +303 -0
- package/src/kernel/pluginGuard.js +37 -0
- package/src/kernel/pluginLoader.js +112 -0
- package/src/kernel/pluginState.js +99 -0
- package/src/kernel/scheduler.js +48 -0
- package/src/locales/en.json +64 -0
- package/src/locales/es.json +59 -0
- package/src/locales/pt.json +64 -0
- package/src/logger/logger.js +31 -0
- package/src/main.js +105 -0
- package/src/utils/file.js +9 -0
- package/src/utils/getChatId.js +3 -0
- package/src/utils/get_id.js +177 -0
- package/src/utils/pluginI18n.js +129 -0
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
|
|
19
|
+
const PLUGINS_DIR = path.join(PATHS.HOME, "plugins");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Each entry in registry:
|
|
23
|
+
* {
|
|
24
|
+
* name: string,
|
|
25
|
+
* status: "active" | "disabled" | "error",
|
|
26
|
+
* run: async function({ msg, chat, api }) — plugin default function
|
|
27
|
+
* exports: any — what plugin exposed via `export const api = { ... }`
|
|
28
|
+
* error: Error | null
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* @type {Map<string, object>}
|
|
32
|
+
*/
|
|
33
|
+
export const pluginRegistry = new Map();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load all active plugins listed in `activePlugins`.
|
|
37
|
+
* Called once during bot initialization.
|
|
38
|
+
*
|
|
39
|
+
* @param {string[]} activePlugins — active plugin names (from .conf)
|
|
40
|
+
*/
|
|
41
|
+
export async function loadPlugins(activePlugins) {
|
|
42
|
+
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
43
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const name of activePlugins) {
|
|
47
|
+
await loadPlugin(name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const total = pluginRegistry.size;
|
|
51
|
+
const ativos = [...pluginRegistry.values()].filter(p => p.status === "active").length;
|
|
52
|
+
const erros = total - ativos;
|
|
53
|
+
|
|
54
|
+
logger.success(t("system.pluginsLoaded", {
|
|
55
|
+
count: ativos,
|
|
56
|
+
errors: erros ? t("system.pluginsLoadedWithErrors", { count: erros }) : ""
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Call setup(api) on all plugins that export it.
|
|
62
|
+
* Executed once after bot connects to WhatsApp.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} api — api without message context (only sendTo, log, schedule...)
|
|
65
|
+
*/
|
|
66
|
+
export async function setupPlugins(api) {
|
|
67
|
+
for (const plugin of pluginRegistry.values()) {
|
|
68
|
+
if (plugin.status !== "active" || !plugin.setup) continue;
|
|
69
|
+
try {
|
|
70
|
+
await plugin.setup(api);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logger.error(t("system.pluginSetupFailed", { name: plugin.name, message: err.message }));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Carrega um único plugin pelo nome.
|
|
79
|
+
* @param {string} name
|
|
80
|
+
*/
|
|
81
|
+
async function loadPlugin(name) {
|
|
82
|
+
const pluginPath = path.join(PLUGINS_DIR, name, "index.js");
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(pluginPath)) {
|
|
85
|
+
logger.warn(t("system.pluginNotFound", { name, path: pluginPath }));
|
|
86
|
+
pluginRegistry.set(name, { name, status: "disabled", run: null, exports: null, error: null });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const mod = await import(pathToFileURL(pluginPath).href);
|
|
92
|
+
|
|
93
|
+
// Plugin must export a default function — this is called on every message
|
|
94
|
+
if (typeof mod.default !== "function") {
|
|
95
|
+
throw new Error(`Plugin "${name}" does not export a default function`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pluginRegistry.set(name, {
|
|
99
|
+
name,
|
|
100
|
+
status: "active",
|
|
101
|
+
run: mod.default,
|
|
102
|
+
setup: mod.setup ?? null, // opcional — chamado uma vez na inicialização
|
|
103
|
+
exports: mod.api ?? null,
|
|
104
|
+
error: null,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
logger.info(t("system.pluginLoaded", { name }));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
logger.error(t("system.pluginLoadFailed", { name, message: err.message }));
|
|
110
|
+
pluginRegistry.set(name, { name, status: "error", run: null, exports: null, error: err });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bot": {
|
|
3
|
+
"starting": "Starting ManyBot...",
|
|
4
|
+
"initialized": "Client initialized. Waiting for WhatsApp connection...",
|
|
5
|
+
"ready": "Bot is ready!",
|
|
6
|
+
"error": {
|
|
7
|
+
"uncaught": "Uncaught exception",
|
|
8
|
+
"unhandled": "Unhandled rejection"
|
|
9
|
+
},
|
|
10
|
+
"signal": {
|
|
11
|
+
"sigterm": "Process interrupted. Shutting down..."
|
|
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": "group",
|
|
24
|
+
"from": "From",
|
|
25
|
+
"type": "Type",
|
|
26
|
+
"replyTo": "replies to"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"system": {
|
|
30
|
+
"environment": "Environment: {{platform}} — using {{puppeteer}}",
|
|
31
|
+
"environmentTermux": "Environment: Termux — using system Chromium",
|
|
32
|
+
"connected": "WhatsApp connected and ready!",
|
|
33
|
+
"disconnected": "Disconnected — reason: {{reason}}",
|
|
34
|
+
"reconnecting": "Reconnecting in {{seconds}}s...",
|
|
35
|
+
"reinitializing": "Reinitializing client...",
|
|
36
|
+
"qrSaved": "QR Code saved to: {{path}}",
|
|
37
|
+
"qrOpen": "Open with: termux-open qr.png",
|
|
38
|
+
"qrSaveFailed": "Failed to save QR Code:",
|
|
39
|
+
"qrScan": "Scan the QR Code below:",
|
|
40
|
+
"pairingCodeTitle": "Pairing code:",
|
|
41
|
+
"pairingCodeValue": "→ {{code}} ←",
|
|
42
|
+
"pairingCodeInstructions": "Enter this code in WhatsApp: Menu → Linked devices → Link with phone number",
|
|
43
|
+
"phoneNumberInvalid": "Invalid phone number: {{number}}. Expected format: 5511999999999 (country code + area code + number, no spaces or special characters)",
|
|
44
|
+
"phoneNumberChanged": "Phone number changed. Restarting authentication...",
|
|
45
|
+
"clientId": "Client ID: {{id}}",
|
|
46
|
+
"pluginsFolderNotFound": "Plugins folder not found. No plugins loaded.",
|
|
47
|
+
"pluginsLoaded": "Plugins loaded: {{count}} active{{errors}}",
|
|
48
|
+
"pluginsLoadedWithErrors": ", {{count}} with error",
|
|
49
|
+
"pluginSetupFailed": "Plugin \"{{name}}\" setup failed: {{message}}",
|
|
50
|
+
"pluginNotFound": "Plugin \"{{name}}\" not found at {{path}}",
|
|
51
|
+
"pluginLoaded": "Plugin loaded: {{name}}",
|
|
52
|
+
"pluginLoadFailed": "Failed to load plugin \"{{name}}\": {{message}}",
|
|
53
|
+
"pluginDisabledAfterError": "Plugin \"{{name}}\" disabled after error: {{message}}",
|
|
54
|
+
"schedulerInvalidCron": "Plugin \"{{name}}\" registered invalid cron expression: \"{{expression}}\"",
|
|
55
|
+
"schedulerError": "Plugin \"{{name}}\" scheduling error: {{message}}",
|
|
56
|
+
"schedulerRegistered": "Schedule registered — plugin \"{{name}}\" → \"{{expression}}\"",
|
|
57
|
+
"downloadJobFailed": "Download job failed — {{message}}"
|
|
58
|
+
},
|
|
59
|
+
"errors": {
|
|
60
|
+
"pluginLoad": "Failed to load plugin",
|
|
61
|
+
"messageProcess": "Failed to process message",
|
|
62
|
+
"stack": "Stack"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bot": {
|
|
3
|
+
"starting": "Iniciando ManyBot...",
|
|
4
|
+
"initialized": "Cliente inicializado. Aguardando conexão com WhatsApp...",
|
|
5
|
+
"ready": "Bot está pronto!",
|
|
6
|
+
"error": {
|
|
7
|
+
"uncaught": "Exceção não capturada",
|
|
8
|
+
"unhandled": "Rejeição não tratada"
|
|
9
|
+
},
|
|
10
|
+
"signal": {
|
|
11
|
+
"sigterm": "Processo interrompido. Desligando..."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"log": {
|
|
15
|
+
"info": "INFO",
|
|
16
|
+
"success": "OK",
|
|
17
|
+
"warn": "WARN",
|
|
18
|
+
"error": "ERRO",
|
|
19
|
+
"msg": "MSG",
|
|
20
|
+
"cmd": "CMD",
|
|
21
|
+
"done": "DONE",
|
|
22
|
+
"context": {
|
|
23
|
+
"group": "grupo",
|
|
24
|
+
"from": "De",
|
|
25
|
+
"type": "Tipo",
|
|
26
|
+
"replyTo": "Responde"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"system": {
|
|
30
|
+
"environment": "Ambiente: {{platform}} — usando {{puppeteer}}",
|
|
31
|
+
"environmentTermux": "Ambiente: Termux — usando Chromium do sistema",
|
|
32
|
+
"connected": "WhatsApp conectado e pronto!",
|
|
33
|
+
"disconnected": "Desconectado — motivo: {{reason}}",
|
|
34
|
+
"reconnecting": "Reconectando em {{seconds}}s...",
|
|
35
|
+
"reinitializing": "Reinicializando cliente...",
|
|
36
|
+
"qrSaved": "QR Code salvo em: {{path}}",
|
|
37
|
+
"qrOpen": "Abra com: termux-open qr.png",
|
|
38
|
+
"qrSaveFailed": "Falha ao salvar QR Code:",
|
|
39
|
+
"qrScan": "Escaneie o QR Code abaixo:",
|
|
40
|
+
"pairingCodeTitle": "Código de pareamento:",
|
|
41
|
+
"pairingCodeValue": "→ {{code}} ←",
|
|
42
|
+
"pairingCodeInstructions": "Digite este código no WhatsApp: Menu → Dispositivos conectados → Conectar com número de telefone",
|
|
43
|
+
"phoneNumberInvalid": "Número de telefone inválido: {{number}}. Formato esperado: 5511999999999 (código do país + DDD + número, sem espaços ou caracteres especiais)",
|
|
44
|
+
"phoneNumberChanged": "Número de telefone alterado. Reiniciando autenticação...",
|
|
45
|
+
"clientId": "Client ID: {{id}}",
|
|
46
|
+
"pluginsFolderNotFound": "Pasta de plugins não encontrada. Nenhum plugin carregado.",
|
|
47
|
+
"pluginsLoaded": "Plugins carregados: {{count}} ativos{{errors}}",
|
|
48
|
+
"pluginsLoadedWithErrors": ", {{count}} com erro",
|
|
49
|
+
"pluginSetupFailed": "Falha na configuração do plugin \"{{name}}\": {{message}}",
|
|
50
|
+
"pluginNotFound": "Plugin \"{{name}}\" não encontrado em {{path}}",
|
|
51
|
+
"pluginLoaded": "Plugin carregado: {{name}}",
|
|
52
|
+
"pluginLoadFailed": "Falha ao carregar plugin \"{{name}}\": {{message}}",
|
|
53
|
+
"pluginDisabledAfterError": "Plugin \"{{name}}\" desativado após erro: {{message}}",
|
|
54
|
+
"schedulerInvalidCron": "Plugin \"{{name}}\" registrou expressão cron inválida: \"{{expression}}\"",
|
|
55
|
+
"schedulerError": "Erro no agendamento do plugin \"{{name}}\": {{message}}",
|
|
56
|
+
"schedulerRegistered": "Agendamento registrado — plugin \"{{name}}\" → \"{{expression}}\"",
|
|
57
|
+
"downloadJobFailed": "Falha no job de download — {{message}}"
|
|
58
|
+
},
|
|
59
|
+
"errors": {
|
|
60
|
+
"pluginLoad": "Falha ao carregar plugin",
|
|
61
|
+
"messageProcess": "Falha ao processar mensagem",
|
|
62
|
+
"stack": "Stack"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
|
|
18
|
+
cmd: (cmd, extra = "") =>
|
|
19
|
+
console.log(
|
|
20
|
+
`${c.gray}${now()}${c.reset}${c.yellow}CMD ${c.reset}` +
|
|
21
|
+
`${c.bold}${cmd}${c.reset}` +
|
|
22
|
+
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
|
|
23
|
+
),
|
|
24
|
+
|
|
25
|
+
done: (cmd, detail = "") =>
|
|
26
|
+
console.log(
|
|
27
|
+
`${c.gray}${now()}${c.reset}${c.green}DONE ${c.reset}` +
|
|
28
|
+
`${c.dim}${cmd}${c.reset}` +
|
|
29
|
+
(detail ? ` — ${detail}` : "")
|
|
30
|
+
),
|
|
31
|
+
};
|
package/src/main.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* main.js
|
|
5
|
+
*
|
|
6
|
+
* ManyBot entry point.
|
|
7
|
+
* Initializes WhatsApp client and loads plugins.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import Module from "module";
|
|
11
|
+
import path from "path";
|
|
12
|
+
|
|
13
|
+
process.env.NODE_PATH = path.resolve(process.cwd(), "node_modules");
|
|
14
|
+
Module._initPaths();
|
|
15
|
+
|
|
16
|
+
import client, { handleQR, handlePairingCode } from "#client/whatsappClient";
|
|
17
|
+
import { handleMessage } from "#kernel/messageHandler";
|
|
18
|
+
import { loadPlugins, setupPlugins } from "#kernel/pluginLoader";
|
|
19
|
+
import { buildSetupApi } from "#manyapi";
|
|
20
|
+
import { logger } from "#logger";
|
|
21
|
+
import { PLUGINS } from "#config";
|
|
22
|
+
import { t } from "#i18n";
|
|
23
|
+
import { printBanner } from "#client/banner";
|
|
24
|
+
import { CLIENT_ID } from "#config";
|
|
25
|
+
|
|
26
|
+
logger.info(t("bot.starting"));
|
|
27
|
+
|
|
28
|
+
// Global safety net — no error should crash the bot
|
|
29
|
+
process.on("uncaughtException", (err) => {
|
|
30
|
+
logger.error(`${t("bot.error.uncaught")} — ${err.message}`,
|
|
31
|
+
`\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
process.on("unhandledRejection", (reason) => {
|
|
35
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
36
|
+
logger.error(`${t("bot.error.unhandled")} — ${msg}`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Clean shutdown
|
|
40
|
+
process.on("SIGTERM", async () => {
|
|
41
|
+
logger.error(t("bot.signal.sigterm"));
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let state = "BOOT";
|
|
46
|
+
// BOOT → AUTH → SYNC → READY
|
|
47
|
+
|
|
48
|
+
function setState(next) {
|
|
49
|
+
state = next;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
client.on("loading_screen", (p, msg) => {
|
|
53
|
+
setState("SYNC");
|
|
54
|
+
logger.info(`loading ${p}% ${msg}`);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
client.on("ready", async () => {
|
|
58
|
+
setState("READY_INIT");
|
|
59
|
+
|
|
60
|
+
logger.success(t("system.connected"));
|
|
61
|
+
logger.info(t("system.clientId", { id: CLIENT_ID }));
|
|
62
|
+
|
|
63
|
+
printBanner();
|
|
64
|
+
|
|
65
|
+
await loadPlugins(PLUGINS);
|
|
66
|
+
await setupPlugins(buildSetupApi(client));
|
|
67
|
+
|
|
68
|
+
// buffer anti-replay / sync ghost messages
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
setState("READY");
|
|
71
|
+
}, 2000);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
client.on("message_create", async (msg) => {
|
|
75
|
+
if (state !== "READY") return;
|
|
76
|
+
|
|
77
|
+
if (!msg.body && !msg.hasMedia) return;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await handleMessage(msg);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
logger.error(err);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
client.on("disconnected", (reason) => {
|
|
87
|
+
logger.warn(t("system.disconnected", { reason }));
|
|
88
|
+
logger.info(t("system.reconnecting", { seconds: 5 }));
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
logger.info(t("system.reinitializing"));
|
|
91
|
+
client.initialize();
|
|
92
|
+
}, 5000);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// -- Events ----------------------------------------------------
|
|
96
|
+
client.on("code", (code) => {
|
|
97
|
+
handlePairingCode(code);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
client.on("qr", (qr) => {
|
|
101
|
+
handleQR(qr);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
client.initialize();
|
|
105
|
+
logger.info(t("bot.initialized"));
|