@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.
Files changed (51) hide show
  1. package/README.md +5 -7
  2. package/dist/client/banner.js +35 -0
  3. package/dist/client/store.js +135 -0
  4. package/dist/config.js +363 -0
  5. package/dist/core/adapter.js +12 -0
  6. package/dist/core/capabilities.js +16 -0
  7. package/dist/core/types.js +6 -0
  8. package/dist/download/queue.js +49 -0
  9. package/dist/drivers/index.js +14 -0
  10. package/dist/drivers/patches/index.js +9 -0
  11. package/dist/drivers/patches/libsignal.js +16 -0
  12. package/dist/drivers/patches/patch.js +1 -0
  13. package/dist/drivers/whatsapp/adapter.js +7 -0
  14. package/dist/drivers/whatsapp/api/index.js +1311 -0
  15. package/dist/drivers/whatsapp/index.js +164 -0
  16. package/dist/drivers/whatsapp/loginPrompt.js +81 -0
  17. package/dist/drivers/whatsapp/messageHandler.js +111 -0
  18. package/dist/drivers/whatsapp/sdk/baileysSock.js +124 -0
  19. package/dist/i18n/index.js +202 -0
  20. package/dist/kernel/pluginApi.js +11 -0
  21. package/dist/kernel/pluginGuard.js +88 -0
  22. package/dist/kernel/pluginLoader.js +322 -0
  23. package/dist/kernel/scheduler.js +110 -0
  24. package/dist/kernel/sendGuard.js +121 -0
  25. package/dist/kernel/settingsDb.js +205 -0
  26. package/{src → dist}/locales/en.json +18 -33
  27. package/dist/locales/es.json +52 -0
  28. package/{src → dist}/locales/pt.json +18 -34
  29. package/dist/logger/logger.js +16 -0
  30. package/dist/main.js +82 -0
  31. package/dist/types.js +16 -0
  32. package/{src → dist}/utils/file.js +3 -3
  33. package/package.json +35 -26
  34. package/src/client/banner.js +0 -57
  35. package/src/client/whatsappClient.js +0 -185
  36. package/src/config.js +0 -288
  37. package/src/download/queue.js +0 -55
  38. package/src/i18n/index.js +0 -235
  39. package/src/kernel/messageHandler.js +0 -88
  40. package/src/kernel/pluginApi.js +0 -630
  41. package/src/kernel/pluginGuard.js +0 -78
  42. package/src/kernel/pluginLoader.js +0 -177
  43. package/src/kernel/pluginState.js +0 -99
  44. package/src/kernel/scheduler.js +0 -48
  45. package/src/kernel/sendGuard.js +0 -166
  46. package/src/locales/es.json +0 -62
  47. package/src/logger/logger.js +0 -32
  48. package/src/main.js +0 -136
  49. package/src/utils/getChatId.js +0 -3
  50. package/src/utils/get_id.js +0 -177
  51. package/src/utils/pluginI18n.js +0 -129
@@ -1,57 +0,0 @@
1
- const C = {
2
- reset: "\x1b[0m",
3
- bold: "\x1b[1m",
4
-
5
- blue: "\x1b[94m",
6
- magenta: "\x1b[95m",
7
- cyan: "\x1b[96m",
8
- gray: "\x1b[90m",
9
- yellow: "\x1b[93m",
10
- };
11
-
12
- import { readFileSync } from "fs";
13
- import { fileURLToPath } from "url";
14
- import path from "path";
15
-
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = path.dirname(__filename);
18
-
19
- const v = JSON.parse(
20
- readFileSync(
21
- path.join(__dirname, "../../package.json"),
22
- "utf8"
23
- )
24
- ).version;
25
-
26
- export function printBanner() {
27
- const banner = [
28
- ` _ _ `,
29
- ` | | | | `,
30
- ` _ __ ___ __ _ _ __ _ _| |__ ___ | |_`,
31
- `| '_ \` _ \\ / _\` | '_ \\| | | | '_ \\ / _ \\| __`,
32
- `| | | | | | (_| | | | | |_| | |_) | (_) | |_`,
33
- `|_| |_| |_|\\__,_|_| |_|\\__, |_.__/ \\___/ \\__`,
34
- ` __/ | `,
35
- ` ${v} |___/ `
36
- ];
37
-
38
- console.log(`${C.bold}${C.blue}`);
39
- console.log(banner.join("\n"));
40
- console.log(C.reset);
41
-
42
- console.log(
43
- ` made with ${C.magenta}<3${C.reset} by ${C.bold}${C.cyan}SyntaxError!${C.reset} ${C.gray}<me@stxerr.dev>${C.reset}`
44
- );
45
-
46
- console.log();
47
-
48
- console.log(
49
- ` ${C.gray}website${C.reset} : ${C.yellow}https://manybot.stxerr.dev${C.reset}`
50
- );
51
-
52
- console.log(
53
- ` ${C.gray}github ${C.reset} : ${C.yellow}https://github.com/many-bot${C.reset}`
54
- );
55
-
56
- console.log();
57
- }
@@ -1,185 +0,0 @@
1
- /* whatsappClient
2
- *
3
- * Initialize client and connect to WhatsApp
4
- *
5
- * if PHONE_NUMBER is set on config, it will request a verficiation code
6
- * but if it is not, it will display a QR Code on the screen to scan using your phone
7
- *
8
- * */
9
-
10
- import pkg from "whatsapp-web.js";
11
- import fs from "fs";
12
- import path from "path";
13
- import { fileURLToPath } from "url";
14
- import { PHONE_NUMBER, CLIENT_ID } from "#config";
15
- import { logger } from "#logger";
16
- import qrcode from "qrcode-terminal";
17
- import { t } from "#i18n"
18
- import { CONFIG_DIR } from "#config";
19
- import { extract } from "tar";
20
- import { pipeline } from "node:stream/promises";
21
- import { Readable } from "node:stream";
22
- import { Transform } from "node:stream";
23
- const os = process.platform;
24
-
25
- const __filename = fileURLToPath(import.meta.url);
26
- const __dirname = path.dirname(__filename);
27
-
28
- const DATA_PATH = path.join(CONFIG_DIR, "sessions");
29
-
30
- const chromeDir = path.join(CONFIG_DIR, "chrome");
31
-
32
- const bins = {
33
- linux: "chrome",
34
- win32: "chrome.exe"
35
- }
36
-
37
- const chrome = path.join(chromeDir, bins[os]);
38
-
39
- if (!fs.existsSync(chrome)) {
40
- try {
41
- logger.warn(t("errors.chromeNotFound"));
42
- const tmpDir = "/tmp/manybot-chrome";
43
- const tmpPath = path.join(tmpDir, "chrome.tar.gz");
44
- const urls = {
45
- linux: "https://api.manybot.stxerr.dev/download-chrome-linux",
46
- win32: "https://api.manybot.stxerr.dev/download-chrome-win"
47
- }
48
-
49
- const url = urls[os];
50
- if (!url) throw new Error(t("errors.OSNotSupported", { os }));
51
-
52
- fs.mkdirSync(tmpDir, { recursive: true });
53
-
54
- const res = await fetch(url);
55
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
56
-
57
- const expected = Number(res.headers.get("content-length"));
58
- let received = 0;
59
- const frames = ["|","/","-","\\"];
60
- let frame = 0;
61
- let spinner;
62
-
63
- const file = fs.createWriteStream(tmpPath);
64
-
65
- spinner = setInterval(() => {
66
- const pct = expected
67
- ? ` ${Math.floor((received / expected) * 100)}%`
68
- : ` ${(received / 1024 / 1024).toFixed(1)}MB`;
69
- process.stderr.write(`\r${frames[frame++ % frames.length]} Baixando Chrome...${pct}`);
70
- }, 80);
71
-
72
- const counter = new Transform({
73
- transform(chunk, _enc, cb) {
74
- received += chunk.length;
75
- cb(null, chunk);
76
- }
77
- });
78
-
79
- try {
80
- await pipeline(
81
- Readable.fromWeb(res.body),
82
- counter,
83
- file
84
- );
85
- } finally {
86
- clearInterval(spinner);
87
- process.stderr.write("\r\x1b[2K");
88
- }
89
-
90
- if (expected && received !== expected) {
91
- throw new Error("Download incompleto");
92
- }
93
-
94
- await extract({ file: tmpPath, cwd: CONFIG_DIR, gzip: true });
95
- } catch (err) {
96
- throw new Error(t("errors.couldNotDownloadChrome") + err.message);
97
- }
98
-
99
- }
100
- export const { Client, LocalAuth, MessageMedia } = pkg;
101
-
102
- // -- Instance --------------------------------------------------
103
- const clientOptions = {
104
- authStrategy: new LocalAuth({
105
- clientId: CLIENT_ID,
106
- dataPath: DATA_PATH
107
- }),
108
- puppeteer: {
109
- executablePath: chrome,
110
- headless: true,
111
- args: [
112
- '--no-sandbox',
113
- '--disable-setuid-sandbox'
114
- ],
115
- },
116
- };
117
-
118
- // -- Qr Handle --------------------------------------------------
119
- export function handleQR(qr) {
120
- logger.info(t("system.qrScan"));
121
- qrcode.generate(qr, { small: true });
122
- }
123
-
124
- // -- Handle pairing code ---------------------------------------
125
- export function handlePairingCode(code) {
126
- logger.info(t("system.pairingCodeTitle"));
127
- logger.info(t("system.pairingCodeValue", { code: code }));
128
- logger.info(t("system.pairingCodeInstructions"));
129
- }
130
-
131
- // -- Phone Number Validation ------------------------------------
132
- const AUTH_STATE_PATH = path.join(__dirname, `../../.auth_${CLIENT_ID}.json`);
133
-
134
- // Validates if phone string has 10-15 characters
135
- function isValidPhoneNumber(phone) {
136
- if (!phone || typeof phone !== 'string') return false;
137
- const phoneRegex = /^\d{10,15}$/;
138
- return phoneRegex.test(phone);
139
- }
140
-
141
- // Checks if phone number changed since last authentication
142
- function hasPhoneNumberChanged(currentPhone) {
143
- try {
144
- if (!fs.existsSync(AUTH_STATE_PATH)) return false;
145
- const state = JSON.parse(fs.readFileSync(AUTH_STATE_PATH, 'utf8'));
146
- const storedPhone = state.phoneNumber || null;
147
- return storedPhone !== currentPhone;
148
- } catch {
149
- return false;
150
- }
151
- }
152
-
153
- // Saves phone number to auth state file
154
- function savePhoneNumber(phone) {
155
- try {
156
- fs.writeFileSync(AUTH_STATE_PATH, JSON.stringify({ phoneNumber: phone, savedAt: new Date().toISOString() }));
157
- } catch {
158
- return false;
159
- }
160
- }
161
-
162
- // Check if phone number changed and force re-authentication if needed
163
- if (PHONE_NUMBER && hasPhoneNumberChanged(PHONE_NUMBER)) {
164
- // Delete auth folder to force fresh authentication
165
- const authPath = path.join(__dirname, `../../.wwebjs_auth/session-${CLIENT_ID}`);
166
- if (fs.existsSync(authPath)) {
167
- fs.rmSync(authPath, { recursive: true, force: true });
168
- }
169
- }
170
-
171
- // Add phone number pairing if PHONE_NUMBER is configured and valid
172
- if (PHONE_NUMBER) {
173
- if (isValidPhoneNumber(PHONE_NUMBER)) {
174
- clientOptions.pairWithPhoneNumber = {
175
- phoneNumber: PHONE_NUMBER,
176
- showNotification: true,
177
- };
178
- }
179
- savePhoneNumber(PHONE_NUMBER);
180
- }
181
-
182
- export const client = new Client(clientOptions);
183
-
184
-
185
- export default client;
package/src/config.js DELETED
@@ -1,288 +0,0 @@
1
- /**
2
- * config.js
3
- *
4
- * Loads config from up to four files, merged in strict precedence order:
5
- *
6
- * defaults < legacy .conf < TOML
7
- *
8
- * Legacy files (frozen — no new keys):
9
- * ~/.manybot/manybot.conf
10
- * ~/.manybot/manyplug.conf
11
- *
12
- * TOML files (all new features go here):
13
- * ~/.manybot/manybot.toml
14
- * ~/.manybot/manyplug.toml
15
- *
16
- * The final CONFIG object always has the same shape regardless of which
17
- * files are present. Plugins must never see a structural difference.
18
- */
19
-
20
- import fs from "fs/promises";
21
- import os from "os";
22
- import path from "path";
23
- import { parse as parseToml } from "smol-toml";
24
- import { logger } from "#logger";
25
-
26
- // ---------------------------------------------------------------------------
27
- // Paths
28
- // ---------------------------------------------------------------------------
29
-
30
- export const CONFIG_DIR = path.join(os.homedir(), ".manybot");
31
-
32
- /** @deprecated Use TOML_CONFIG_FILE. Frozen — no new keys. */
33
- export const CONFIG_FILE = path.join(CONFIG_DIR, "manybot.conf");
34
- /** @deprecated Use TOML_PLUGIN_FILE. Frozen — no new keys. */
35
- export const PLUGIN_FILE = path.join(CONFIG_DIR, "manyplug.conf");
36
-
37
- export const TOML_CONFIG_FILE = path.join(CONFIG_DIR, "manybot.toml");
38
- export const TOML_PLUGIN_FILE = path.join(CONFIG_DIR, "manyplug.toml");
39
-
40
- // ---------------------------------------------------------------------------
41
- // Legacy .conf parser (frozen — do not extend)
42
- // ---------------------------------------------------------------------------
43
-
44
- function parseValue(value) {
45
- value = value.trim();
46
- if (
47
- (value.startsWith('"') && value.endsWith('"')) ||
48
- (value.startsWith("'") && value.endsWith("'"))
49
- ) {
50
- value = value.slice(1, -1);
51
- }
52
- if (value === "true") return true;
53
- if (value === "false") return false;
54
- return value;
55
- }
56
-
57
- function stripInlineComment(line) {
58
- let result = "";
59
- let quote = null;
60
- for (let i = 0; i < line.length; i++) {
61
- const ch = line[i];
62
- if ((ch === '"' || ch === "'") && line[i - 1] !== "\\") {
63
- if (quote === ch) quote = null;
64
- else if (!quote) quote = ch;
65
- }
66
- if (ch === "#" && !quote) break;
67
- result += ch;
68
- }
69
- return result.trim();
70
- }
71
-
72
- function parseConf(raw) {
73
- const lines = raw.split(/\r?\n/);
74
- const mergedLines = [];
75
- let insideList = false;
76
- let buffer = "";
77
-
78
- for (let line of lines) {
79
- line = stripInlineComment(line);
80
- if (!line) continue;
81
-
82
- if (!insideList) {
83
- if (/=\s*\[$/.test(line)) {
84
- insideList = true;
85
- buffer = line;
86
- } else {
87
- mergedLines.push(line);
88
- }
89
- } else {
90
- buffer += " " + line;
91
- if (line.includes("]")) {
92
- mergedLines.push(buffer);
93
- buffer = "";
94
- insideList = false;
95
- }
96
- }
97
- }
98
-
99
- const config = {};
100
- for (const line of mergedLines) {
101
- const idx = line.indexOf("=");
102
- if (idx === -1) continue;
103
- const key = line.slice(0, idx).trim();
104
- let value = line.slice(idx + 1).trim();
105
-
106
- if (value.startsWith("[") && value.endsWith("]")) {
107
- config[key] = value
108
- .slice(1, -1)
109
- .split(",")
110
- .map(v => parseValue(v))
111
- .filter(v => v !== "");
112
- continue;
113
- }
114
- config[key] = parseValue(value);
115
- }
116
- return config;
117
- }
118
-
119
- // ---------------------------------------------------------------------------
120
- // TOML migration
121
- // ---------------------------------------------------------------------------
122
-
123
- function escapeTomlString(s) {
124
- return s
125
- .replaceAll("\\", "\\\\")
126
- .replaceAll('"', '\\"');
127
- }
128
-
129
- function toToml(obj) {
130
- return Object.entries(obj)
131
- .map(([k, v]) => {
132
- if (Array.isArray(v))
133
- return `${k} = [${v.map(JSON.stringify).join(", ")}]`;
134
-
135
- if (v == null || v === "")
136
- return `${k} = ""`;
137
-
138
- return typeof v === "string"
139
- ? `${k} = "${escapeTomlString(v)}"`
140
- : `${k} = ${v}`;
141
- })
142
- .join("\n") + "\n";
143
- }
144
-
145
- async function migrateLegacyIfNeeded() {
146
- if (await fileExists(TOML_CONFIG_FILE))
147
- return;
148
-
149
- const migrate = async (src, dest, omit = []) => {
150
- const raw = await readFileSafe(src);
151
- if (!raw) return false;
152
-
153
- const cfg = parseConf(raw);
154
-
155
- for (const k of omit)
156
- delete cfg[k];
157
-
158
- await fs.writeFile(dest, toToml(cfg), "utf8");
159
- await fs.rename(src, `${src}.bak`);
160
-
161
- return true;
162
- };
163
-
164
- const migrated =
165
- await migrate(CONFIG_FILE, TOML_CONFIG_FILE, ["PLUGINS"]) ||
166
- await migrate(PLUGIN_FILE, TOML_PLUGIN_FILE);
167
-
168
- if (migrated)
169
- logger.success("Config migrated to TOML");
170
- }
171
-
172
- // ---------------------------------------------------------------------------
173
- // File helpers
174
- // ---------------------------------------------------------------------------
175
-
176
- async function fileExists(file) {
177
- try { await fs.stat(file); return true; }
178
- catch { return false; }
179
- }
180
-
181
- async function readFileSafe(file) {
182
- try {
183
- return await fs.readFile(file, "utf-8");
184
- } catch (err) {
185
- if (err.code !== "ENOENT") logger.warn(`Error reading ${file}: ${err.message}`);
186
- return null;
187
- }
188
- }
189
-
190
- // ---------------------------------------------------------------------------
191
- // Bootstrap: ensure at least one config file exists
192
- // ---------------------------------------------------------------------------
193
-
194
- const DEFAULT_TOML =
195
- `# ManyBot configuration file
196
- # See https://manybot.stxerr.dev/docs/config to learn more
197
-
198
- CLIENT_ID = "manybot"
199
- CMD_PREFIX = "!"
200
- CHATS = []
201
- LANGUAGE = "en"
202
- PHONE_NUMBER = ""
203
- `;
204
-
205
- await fs.mkdir(CONFIG_DIR, { recursive: true });
206
-
207
- await migrateLegacyIfNeeded();
208
-
209
- if (!await fileExists(TOML_CONFIG_FILE)) {
210
- logger.warn(`Creating ${TOML_CONFIG_FILE}`);
211
- await fs.writeFile(TOML_CONFIG_FILE, DEFAULT_TOML);
212
- }
213
-
214
- // ---------------------------------------------------------------------------
215
- // Layer 1 — tOML
216
- // ---------------------------------------------------------------------------
217
-
218
- const legacyLayer = {};
219
-
220
- // ---------------------------------------------------------------------------
221
- // Layer 2 — TOML (all new features land here)
222
- // ---------------------------------------------------------------------------
223
-
224
- let tomlLayer = {};
225
-
226
- async function loadToml(file, label) {
227
- const raw = await readFileSafe(file);
228
- if (raw === null) return {};
229
- try {
230
- return parseToml(raw);
231
- } catch (err) {
232
- logger.warn(`Error parsing ${label}: ${err.message}`);
233
- return {};
234
- }
235
- }
236
-
237
- tomlLayer = {
238
- ...await loadToml(TOML_CONFIG_FILE, "manybot.toml"),
239
- ...await loadToml(TOML_PLUGIN_FILE, "manyplug.toml"),
240
- };
241
-
242
- // ---------------------------------------------------------------------------
243
- // Merge + normalize
244
- //
245
- // Normalization runs once on the final merged object so both legacy and TOML
246
- // layers are treated identically. Add new normalization rules here only.
247
- // ---------------------------------------------------------------------------
248
-
249
- const DEFAULTS = {
250
- CMD_PREFIX: "!",
251
- CLIENT_ID: "manybot",
252
- CHATS: [],
253
- PLUGINS: [],
254
- LANGUAGE: "en",
255
- PHONE_NUMBER: null,
256
- };
257
-
258
- function normalize(cfg) {
259
- // Empty string and absent PHONE_NUMBER are both treated as null so plugins
260
- // can always do a simple truthiness check regardless of config source.
261
- if (cfg.PHONE_NUMBER === "") cfg.PHONE_NUMBER = null;
262
- return cfg;
263
- }
264
-
265
- export const CONFIG = normalize({
266
- ...DEFAULTS,
267
- ...legacyLayer, // legacy .conf overrides defaults
268
- ...tomlLayer, // TOML overrides legacy .conf
269
- });
270
-
271
- // ---------------------------------------------------------------------------
272
- // Named exports — identical shape regardless of config source
273
- // ---------------------------------------------------------------------------
274
-
275
- export const CLIENT_ID = CONFIG.CLIENT_ID;
276
- export const CMD_PREFIX = CONFIG.CMD_PREFIX;
277
- export const CHATS = CONFIG.CHATS;
278
- export const PLUGINS = CONFIG.PLUGINS;
279
- export const LANGUAGE = CONFIG.LANGUAGE;
280
- export const PHONE_NUMBER = CONFIG.PHONE_NUMBER;
281
-
282
- export const PATHS = {
283
- HOME: CONFIG_DIR,
284
- CONFIG_FILE,
285
- PLUGIN_FILE,
286
- TOML_CONFIG_FILE,
287
- TOML_PLUGIN_FILE,
288
- };
@@ -1,55 +0,0 @@
1
- /**
2
- * src/download/queue.js
3
- *
4
- * Sequential execution queue for heavy jobs (downloads, conversions).
5
- * Ensures only one job runs at a time — without overloading yt-dlp or ffmpeg.
6
- *
7
- * Plugin passes a `workFn` that does everything: download, convert, send.
8
- * Queue only handles sequence and error handling.
9
- *
10
- * Usage:
11
- * import { enqueue } from "../../src/download/queue.js";
12
- * enqueue(async () => { ... all plugin logic ... }, onError);
13
- */
14
-
15
- import { logger } from "#logger";
16
- import { t } from "#i18n";
17
-
18
- /**
19
- * @typedef {{
20
- * workFn: () => Promise<void>,
21
- * errorFn: (err: Error) => Promise<void>,
22
- * }} Job
23
- */
24
-
25
- /** @type {Job[]} */
26
- let queue = [];
27
- let processing = false;
28
-
29
- /**
30
- * Add job to queue and start processing if idle.
31
- *
32
- * @param {Function} workFn — async () => void — all plugin logic
33
- * @param {Function} errorFn — async (err) => void — called if workFn throws
34
- */
35
- export function enqueue(workFn, errorFn) {
36
- queue.push({ workFn, errorFn });
37
- if (!processing) processQueue();
38
- }
39
-
40
- async function processQueue() {
41
- processing = true;
42
- while (queue.length) {
43
- await processJob(queue.shift());
44
- }
45
- processing = false;
46
- }
47
-
48
- async function processJob({ workFn, errorFn }) {
49
- try {
50
- await workFn();
51
- } catch (err) {
52
- logger.error(t("system.downloadJobFailed", { message: err.message }));
53
- try { await errorFn(err); } catch { }
54
- }
55
- }