@manybot/manybot 5.0.0 → 5.2.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 CHANGED
@@ -6,7 +6,7 @@
6
6
  ![npm 9+](https://img.shields.io/badge/npm-9+-CB3837?logo=npm&logoColor=white)
7
7
  ![GPL v3](https://img.shields.io/badge/License-GPL--v3-blue.svg)
8
8
  ![Linux](https://img.shields.io/badge/Linux%20%7C%20Windows-lightgrey)
9
- ![whatsapp-web.js](https://img.shields.io/badge/WhatsApp-25D366?logo=whatsapp&logoColor=white)
9
+ ![Baileys](https://img.shields.io/badge/WhatsApp-25D366?logo=whatsapp&logoColor=white)
10
10
 
11
11
  </div>
12
12
 
@@ -27,9 +27,9 @@ npm install -g @manybot/manyplug
27
27
  manybot
28
28
  ```
29
29
 
30
- On first run, a configuration file is created at `~/.manybot/manybot.conf`. Edit it to set up your preferences and list the plugins you want to load.
30
+ On first run, a configuration file is created at `~/.manybot/manybot.toml`. Edit it to set up your preferences.
31
31
 
32
- For detailed setup instructions, see the **[documentation](https://manybot.stxerr.dev/docs/)**.
32
+ For detailed setup instructions, see the **[documentation](https://manybot.org/docs/)**.
33
33
 
34
34
  ## Plugins
35
35
 
@@ -39,16 +39,14 @@ ManyBot's functionality comes from plugins. Install them with ManyPlug:
39
39
  manyplug install <plugin-name>
40
40
  ```
41
41
 
42
- Browse available plugins at **[manybot.stxerr.dev/plugins](https://manybot.stxerr.dev/plugins/)**.
43
-
44
- Want to build your own? The plugin API lets you add commands and features without touching the bot's core - *documentation coming soon*.
42
+ Browse available plugins at **[manybot.org/plugins](https://manybot.org/plugins/)**.
45
43
 
46
44
  ## Contributing
47
45
 
48
46
  All kinds of contributions are welcome:
49
47
 
50
48
  - **Bug reports and feature requests**: open an issue on GitHub or Codeberg
51
- - **Code**: pull requests are welcome on [GitHub](https://github.com/many-bot/manybot) or [Codeberg](https://codeberg.org/many-bot/manybot); patches by email (`devel+manybot@stxerr.dev`) are also accepted - subscribe to the mailing list [here](https://list.stxerr.dev).
49
+ - **Code**: pull requests are welcome on [GitHub](https://github.com/many-bot/manybot) or [Codeberg](https://codeberg.org/many-bot/manybot); patches by email (`devel+manybot.org`) are also accepted - subscribe to the mailing list [here](https://list.stxerr.dev).
52
50
  - **Plugins**: submit your plugin to [manyplug-repo](https://github.com/many-bot/manyplug-repo), which has instructions on how to do it
53
51
  - **Anything else**: suggestions, translations, documentation fixes - reach out by email or open an issue
54
52
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "SyntaxError!",
6
6
  "email": "me@stxerr.dev"
7
7
  },
8
- "version": "5.0.0",
8
+ "version": "5.2.0",
9
9
  "license": "GPL-3.0-only",
10
10
  "private": false,
11
11
  "engines": {
@@ -17,38 +17,49 @@
17
17
  },
18
18
  "type": "module",
19
19
  "bin": {
20
- "manybot": "./src/main.js"
20
+ "manybot": "dist/main.js"
21
21
  },
22
22
  "files": [
23
- "src/client",
24
- "src/config.js",
25
- "src/download",
26
- "src/i18n",
27
- "src/kernel",
28
- "src/locales",
29
- "src/logger",
30
- "src/main.js",
31
- "src/utils",
23
+ "dist/",
32
24
  "README.md",
33
- "README_EN.md",
34
- "latest",
35
25
  "LICENSE"
36
26
  ],
27
+ "scripts": {
28
+ "build": "tsc && node -e \"fs.mkdirSync('dist/locales', { recursive: true }); fs.cpSync('src/locales', 'dist/locales', { recursive: true });\"",
29
+ "start": "node dist/main.js",
30
+ "typecheck": "tsc --noEmit"
31
+ },
32
+ "devDependencies": {
33
+ "@types/better-sqlite3": "^7.6.12",
34
+ "@types/node": "^22.0.0",
35
+ "tsx": "^4.19.2",
36
+ "typescript": "^5.7.3"
37
+ },
37
38
  "dependencies": {
39
+ "@clack/prompts": "^0.10.0",
40
+ "@hapi/boom": "^10.0.1",
41
+ "@whiskeysockets/baileys": "^6.7.23",
42
+ "better-sqlite3": "^12.11.1",
43
+ "node-cron": "^4.6.0",
38
44
  "node-webpmux": "^3.2.1",
45
+ "pino": "^10.3.1",
39
46
  "qrcode-terminal": "^0.12.0",
40
- "tar": "^7.5.16",
41
- "whatsapp-web.js": "^1.24.0"
47
+ "smol-toml": "^1.7.0"
42
48
  },
43
49
  "imports": {
44
- "#client/*": "./src/client/*.js",
45
- "#kernel/*": "./src/kernel/*.js",
46
- "#manyapi": "./src/kernel/pluginApi.js",
47
- "#logger": "./src/logger/logger.js",
48
- "#utils/*": "./src/utils/*.js",
49
- "#i18n": "./src/i18n/index.js",
50
- "#download": "./src/download/queue.js",
51
- "#config": "./src/config.js",
52
- "#main": "./src/main.js"
50
+ "#drivers/*": "./dist/drivers/*",
51
+ "#core/*": "./dist/core/*",
52
+ "#client/*": "./dist/client/*",
53
+ "#kernel/*": "./dist/kernel/*",
54
+ "#manyapi": "./dist/kernel/pluginApi.js",
55
+ "#settingsdb": "./dist/kernel/settingsDb.js",
56
+ "#sendguard": "./dist/kernel/sendGuard.js",
57
+ "#logger": "./dist/logger/logger.js",
58
+ "#utils/*": "./dist/utils/*",
59
+ "#i18n": "./dist/i18n/index.js",
60
+ "#download": "./dist/download/queue.js",
61
+ "#config": "./dist/config.js",
62
+ "#main": "./dist/main.js",
63
+ "#types": "./dist/types.js"
53
64
  }
54
65
  }
@@ -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,178 +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
-
24
- const __filename = fileURLToPath(import.meta.url);
25
- const __dirname = path.dirname(__filename);
26
-
27
- const DATA_PATH = path.join(CONFIG_DIR, "sessions");
28
-
29
- const chrome = path.join(CONFIG_DIR, "chrome/chrome")
30
-
31
- if (!fs.existsSync(chrome)) {
32
- try {
33
- logger.warn(t("errors.chromeNotFound"));
34
- const tmpDir = "/tmp/manybot-chrome";
35
- const tmpPath = path.join(tmpDir, "chrome.tar.gz");
36
- const os = process.platform;
37
- const urls = {
38
- linux: "https://api.manybot.stxerr.dev/download-chrome-linux",
39
- win32: "https://api.manybot.stxerr.dev/download-chrome-win"
40
- }
41
-
42
- const url = urls[os];
43
- if (!url) throw new Error(t("errors.OSNotSupported", { os }));
44
-
45
- fs.mkdirSync(tmpDir, { recursive: true });
46
-
47
- const res = await fetch(url);
48
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
49
-
50
- const expected = Number(res.headers.get("content-length"));
51
- let received = 0;
52
- const frames = ["|","/","-","\\"];
53
- let frame = 0;
54
- let spinner;
55
-
56
- const file = fs.createWriteStream(tmpPath);
57
-
58
- spinner = setInterval(() => {
59
- const pct = expected
60
- ? ` ${Math.floor((received / expected) * 100)}%`
61
- : ` ${(received / 1024 / 1024).toFixed(1)}MB`;
62
- process.stderr.write(`\r${frames[frame++ % frames.length]} Baixando Chrome...${pct}`);
63
- }, 80);
64
-
65
- const counter = new Transform({
66
- transform(chunk, _enc, cb) {
67
- received += chunk.length;
68
- cb(null, chunk);
69
- }
70
- });
71
-
72
- try {
73
- await pipeline(
74
- Readable.fromWeb(res.body),
75
- counter,
76
- file
77
- );
78
- } finally {
79
- clearInterval(spinner);
80
- process.stderr.write("\r\x1b[2K"); // limpa a linha
81
- }
82
-
83
- if (expected && received !== expected) {
84
- throw new Error("Download incompleto");
85
- }
86
-
87
- await extract({ file: tmpPath, cwd: CONFIG_DIR, gzip: true });
88
- } catch (err) {
89
- throw new Error(t("errors.couldNotDownloadChrome") + err.message);
90
- }
91
-
92
- }
93
- export const { Client, LocalAuth, MessageMedia } = pkg;
94
-
95
- // -- Instance --------------------------------------------------
96
- const clientOptions = {
97
- authStrategy: new LocalAuth({
98
- clientId: CLIENT_ID,
99
- dataPath: DATA_PATH
100
- }),
101
- puppeteer: {
102
- executablePath: chrome,
103
- headless: true,
104
- args: [
105
- '--no-sandbox',
106
- '--disable-setuid-sandbox'
107
- ],
108
- },
109
- };
110
-
111
- // -- Qr Handle --------------------------------------------------
112
- export function handleQR(qr) {
113
- logger.info(t("system.qrScan"));
114
- qrcode.generate(qr, { small: true });
115
- }
116
-
117
- // -- Handle pairing code ---------------------------------------
118
- export function handlePairingCode(code) {
119
- logger.info(t("system.pairingCodeTitle"));
120
- logger.info(t("system.pairingCodeValue", { code: code }));
121
- logger.info(t("system.pairingCodeInstructions"));
122
- }
123
-
124
- // -- Phone Number Validation ------------------------------------
125
- const AUTH_STATE_PATH = path.join(__dirname, `../../.auth_${CLIENT_ID}.json`);
126
-
127
- // Validates if phone string has 10-15 characters
128
- function isValidPhoneNumber(phone) {
129
- if (!phone || typeof phone !== 'string') return false;
130
- const phoneRegex = /^\d{10,15}$/;
131
- return phoneRegex.test(phone);
132
- }
133
-
134
- // Checks if phone number changed since last authentication
135
- function hasPhoneNumberChanged(currentPhone) {
136
- try {
137
- if (!fs.existsSync(AUTH_STATE_PATH)) return false;
138
- const state = JSON.parse(fs.readFileSync(AUTH_STATE_PATH, 'utf8'));
139
- const storedPhone = state.phoneNumber || null;
140
- return storedPhone !== currentPhone;
141
- } catch {
142
- return false;
143
- }
144
- }
145
-
146
- // Saves phone number to auth state file
147
- function savePhoneNumber(phone) {
148
- try {
149
- fs.writeFileSync(AUTH_STATE_PATH, JSON.stringify({ phoneNumber: phone, savedAt: new Date().toISOString() }));
150
- } catch {
151
- return false;
152
- }
153
- }
154
-
155
- // Check if phone number changed and force re-authentication if needed
156
- if (PHONE_NUMBER && hasPhoneNumberChanged(PHONE_NUMBER)) {
157
- // Delete auth folder to force fresh authentication
158
- const authPath = path.join(__dirname, `../../.wwebjs_auth/session-${CLIENT_ID}`);
159
- if (fs.existsSync(authPath)) {
160
- fs.rmSync(authPath, { recursive: true, force: true });
161
- }
162
- }
163
-
164
- // Add phone number pairing if PHONE_NUMBER is configured and valid
165
- if (PHONE_NUMBER) {
166
- if (isValidPhoneNumber(PHONE_NUMBER)) {
167
- clientOptions.pairWithPhoneNumber = {
168
- phoneNumber: PHONE_NUMBER,
169
- showNotification: true,
170
- };
171
- }
172
- savePhoneNumber(PHONE_NUMBER);
173
- }
174
-
175
- export const client = new Client(clientOptions);
176
-
177
-
178
- export default client;
package/src/config.js DELETED
@@ -1,190 +0,0 @@
1
- /**
2
- * config.js
3
- *
4
- * Loads:
5
- * ~/.manybot/manybot.conf
6
- * ~/.manybot/manyplug.conf
7
- *
8
- * Merges both files into a single configuration object.
9
- */
10
-
11
- import fs from "fs/promises";
12
- import os from "os";
13
- import path from "path";
14
-
15
- import { logger } from "#logger";
16
-
17
- export const CONFIG_DIR = path.join(os.homedir(), ".manybot");
18
- export const CONFIG_FILE = path.join(CONFIG_DIR, "manybot.conf");
19
- export const PLUGIN_FILE = path.join(CONFIG_DIR, "manyplug.conf");
20
-
21
- /**
22
- * Converts strings to native JS values.
23
- */
24
- function parseValue(value) {
25
- value = value.trim();
26
-
27
- if (
28
- (value.startsWith('"') && value.endsWith('"')) ||
29
- (value.startsWith("'") && value.endsWith("'"))
30
- ) {
31
- value = value.slice(1, -1);
32
- }
33
-
34
- if (value === "true")
35
- return true;
36
-
37
- if (value === "false")
38
- return false;
39
-
40
- return value;
41
- }
42
-
43
- /**
44
- * Reads comments safely.
45
- * Ignores # inside quoted strings.
46
- */
47
- function stripInlineComment(line) {
48
- let result = "";
49
- let quote = null;
50
-
51
- for (let i = 0; i < line.length; i++) {
52
- const ch = line[i];
53
-
54
- if ((ch === '"' || ch === "'") && line[i - 1] !== "\\") {
55
- if (quote === ch)
56
- quote = null;
57
- else if (!quote)
58
- quote = ch;
59
- }
60
-
61
- if (ch === "#" && !quote)
62
- break;
63
-
64
- result += ch;
65
- }
66
-
67
- return result.trim();
68
- }
69
-
70
- /**
71
- * Parses manybot.conf syntax.
72
- */
73
- function parseConf(raw) {
74
- const lines = raw.split(/\r?\n/);
75
-
76
- const mergedLines = [];
77
-
78
- let insideList = false;
79
- let buffer = "";
80
-
81
- for (let line of lines) {
82
- line = stripInlineComment(line);
83
-
84
- if (!line)
85
- continue;
86
-
87
- if (!insideList) {
88
- if (/=\s*\[$/.test(line)) {
89
- insideList = true;
90
- buffer = line;
91
- } else {
92
- mergedLines.push(line);
93
- }
94
- } else {
95
- buffer += " " + line;
96
-
97
- if (line.includes("]")) {
98
- mergedLines.push(buffer);
99
- buffer = "";
100
- insideList = false;
101
- }
102
- }
103
- }
104
-
105
- const config = {};
106
-
107
- for (const line of mergedLines) {
108
- const idx = line.indexOf("=");
109
-
110
- if (idx === -1)
111
- continue;
112
-
113
- const key = line.slice(0, idx).trim();
114
- let value = line.slice(idx + 1).trim();
115
-
116
- if (value.startsWith("[") && value.endsWith("]")) {
117
- config[key] = value
118
- .slice(1, -1)
119
- .split(",")
120
- .map(v => parseValue(v))
121
- .filter(v => v !== "");
122
- continue;
123
- }
124
-
125
- config[key] = parseValue(value);
126
- }
127
-
128
- return config;
129
- }
130
-
131
- async function readFileSafe(file) {
132
- try {
133
- return await fs.readFile(file, "utf-8");
134
- } catch (err) {
135
- if (err.code !== "ENOENT") {
136
- logger.warn(`Error reading ${file}: ${err.message}`);
137
- }
138
- return "";
139
- }
140
- }
141
-
142
- const defaultConfig =
143
- `
144
- # Many bot configuration file
145
- # See https://manybot.stxerr.dev/docs/config to learn more
146
-
147
- CLIENT_ID="manybot"
148
- CMD_PREFIX="!"
149
- CHATS=[]
150
- LANGUAGE=en
151
- PHONE_NUMBER=
152
- `;
153
-
154
- try {
155
- await fs.stat(CONFIG_FILE);
156
- } catch {
157
- logger.warn("Configuration file not found: ", CONFIG_FILE, ". Creating a new one.");
158
-
159
- await fs.mkdir(CONFIG_DIR, { recursive: true });
160
- await fs.writeFile(CONFIG_FILE, defaultConfig);
161
- }
162
-
163
- const baseConfig = await readFileSafe(CONFIG_FILE);
164
- const pluginConfig = await readFileSafe(PLUGIN_FILE);
165
-
166
- export const CONFIG = {
167
- CMD_PREFIX: "!",
168
- CLIENT_ID: "manybot",
169
- CHATS: [],
170
- PLUGINS: [],
171
- LANGUAGE: "en",
172
- PHONE_NUMBER: null,
173
- ...parseConf(baseConfig + "\n" + pluginConfig),
174
- };
175
-
176
- export const CLIENT_ID = CONFIG.CLIENT_ID;
177
- export const CMD_PREFIX = CONFIG.CMD_PREFIX;
178
- export const CHATS = CONFIG.CHATS;
179
- export const PLUGINS = CONFIG.PLUGINS;
180
- export const LANGUAGE = CONFIG.LANGUAGE;
181
- export const PHONE_NUMBER = CONFIG.PHONE_NUMBER;
182
-
183
- /**
184
- * Useful paths for plugins/modules.
185
- */
186
- export const PATHS = {
187
- HOME: CONFIG_DIR,
188
- CONFIG_FILE,
189
- PLUGIN_FILE
190
- };
@@ -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
- }