@manybot/manybot 4.1.3 → 4.3.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 +2 -2
- package/package.json +3 -2
- package/src/client/whatsappClient.js +63 -0
- package/src/config.js +3 -3
- package/src/kernel/pluginApi.js +73 -4
- package/src/locales/en.json +3 -1
- package/src/locales/es.json +3 -1
- package/src/locales/pt.json +3 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|

|
|
@@ -16,7 +16,7 @@ ManyBot is a free and open-source messaging automation ecosystem for online busi
|
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
|
19
|
-
- Node.js >=
|
|
19
|
+
- Node.js >= 20
|
|
20
20
|
- npm
|
|
21
21
|
|
|
22
22
|
## Getting started
|
package/package.json
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
"name": "SyntaxError!",
|
|
6
6
|
"email": "me@stxerr.dev"
|
|
7
7
|
},
|
|
8
|
-
"version": "4.
|
|
8
|
+
"version": "4.3.0",
|
|
9
9
|
"license": "GPL-3.0-only",
|
|
10
10
|
"private": false,
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=
|
|
12
|
+
"node": ">=20"
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"node-webpmux": "^3.2.1",
|
|
39
39
|
"qrcode-terminal": "^0.12.0",
|
|
40
|
+
"tar": "^7.5.16",
|
|
40
41
|
"whatsapp-web.js": "^1.24.0"
|
|
41
42
|
},
|
|
42
43
|
"imports": {
|
|
@@ -15,16 +15,79 @@ import { PHONE_NUMBER, CLIENT_ID } from "#config";
|
|
|
15
15
|
import { logger } from "#logger";
|
|
16
16
|
import qrcode from "qrcode-terminal";
|
|
17
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";
|
|
18
23
|
|
|
19
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
25
|
const __dirname = path.dirname(__filename);
|
|
21
26
|
|
|
27
|
+
const chrome = path.join(CONFIG_DIR, "chrome/chrome")
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(chrome)) {
|
|
30
|
+
try {
|
|
31
|
+
logger.warn(t("errors.chromeNotFound"));
|
|
32
|
+
const tmpDir = "/tmp/manybot-chrome";
|
|
33
|
+
const tmpPath = path.join(tmpDir, "chrome.tar.gz");
|
|
34
|
+
const url = "https://api.manybot.stxerr.dev/download-chrome";
|
|
35
|
+
|
|
36
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
const res = await fetch(url);
|
|
39
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
40
|
+
|
|
41
|
+
const expected = Number(res.headers.get("content-length"));
|
|
42
|
+
let received = 0;
|
|
43
|
+
const frames = ["|","/","-","\\"];
|
|
44
|
+
let frame = 0;
|
|
45
|
+
let spinner;
|
|
46
|
+
|
|
47
|
+
const file = fs.createWriteStream(tmpPath);
|
|
48
|
+
|
|
49
|
+
spinner = setInterval(() => {
|
|
50
|
+
const pct = expected
|
|
51
|
+
? ` ${Math.floor((received / expected) * 100)}%`
|
|
52
|
+
: ` ${(received / 1024 / 1024).toFixed(1)}MB`;
|
|
53
|
+
process.stderr.write(`\r${frames[frame++ % frames.length]} Baixando Chrome...${pct}`);
|
|
54
|
+
}, 80);
|
|
55
|
+
|
|
56
|
+
const counter = new Transform({
|
|
57
|
+
transform(chunk, _enc, cb) {
|
|
58
|
+
received += chunk.length;
|
|
59
|
+
cb(null, chunk);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await pipeline(
|
|
65
|
+
Readable.fromWeb(res.body),
|
|
66
|
+
counter,
|
|
67
|
+
file
|
|
68
|
+
);
|
|
69
|
+
} finally {
|
|
70
|
+
clearInterval(spinner);
|
|
71
|
+
process.stderr.write("\r\x1b[2K"); // limpa a linha
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (expected && received !== expected) {
|
|
75
|
+
throw new Error("Download incompleto");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await extract({ file: tmpPath, cwd: CONFIG_DIR, gzip: true });
|
|
79
|
+
} catch (err) {
|
|
80
|
+
throw new Error(t("errors.couldNotDownloadChrome") + err.message);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
22
84
|
export const { Client, LocalAuth, MessageMedia } = pkg;
|
|
23
85
|
|
|
24
86
|
// -- Instance --------------------------------------------------
|
|
25
87
|
const clientOptions = {
|
|
26
88
|
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
|
27
89
|
puppeteer: {
|
|
90
|
+
executablePath: chrome,
|
|
28
91
|
headless: true,
|
|
29
92
|
args: [
|
|
30
93
|
'--no-sandbox',
|
package/src/config.js
CHANGED
|
@@ -14,9 +14,9 @@ import path from "path";
|
|
|
14
14
|
|
|
15
15
|
import { logger } from "#logger";
|
|
16
16
|
|
|
17
|
-
const CONFIG_DIR = path.join(os.homedir(), ".manybot");
|
|
18
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, "manybot.conf");
|
|
19
|
-
const PLUGIN_FILE = path.join(CONFIG_DIR, "manyplug.conf");
|
|
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
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Converts strings to native JS values.
|
package/src/kernel/pluginApi.js
CHANGED
|
@@ -136,6 +136,70 @@ const log = {
|
|
|
136
136
|
success: (...a) => logger.success(...a),
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
+
// -- Contact API --------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
function buildContactsApi(client) {
|
|
142
|
+
return {
|
|
143
|
+
/**
|
|
144
|
+
* Get a normalized Contact object by ID.
|
|
145
|
+
* @param {string} contactId — serialized ID (e.g. "5511999999999@c.us")
|
|
146
|
+
* @returns {Promise<object|null>}
|
|
147
|
+
*/
|
|
148
|
+
async get(contactId) {
|
|
149
|
+
try {
|
|
150
|
+
const c = await client.getContactById(contactId);
|
|
151
|
+
return {
|
|
152
|
+
id: c.id._serialized,
|
|
153
|
+
number: c.number,
|
|
154
|
+
pushname: c.pushname ?? null,
|
|
155
|
+
name: c.name ?? null,
|
|
156
|
+
shortName: c.shortName ?? null,
|
|
157
|
+
isBusiness: c.isBusiness,
|
|
158
|
+
isEnterprise: c.isEnterprise,
|
|
159
|
+
isBlocked: c.isBlocked,
|
|
160
|
+
isMe: c.isMe,
|
|
161
|
+
isMyContact: c.isMyContact,
|
|
162
|
+
isWAContact: c.isWAContact,
|
|
163
|
+
isUser: c.isUser,
|
|
164
|
+
isGroup: c.isGroup,
|
|
165
|
+
};
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get the profile picture URL of a contact.
|
|
173
|
+
* Uses Contact#getProfilePicUrl() — respects privacy settings.
|
|
174
|
+
* @param {string} contactId
|
|
175
|
+
* @returns {Promise<string|null>}
|
|
176
|
+
*/
|
|
177
|
+
async getProfilePicUrl(contactId) {
|
|
178
|
+
try {
|
|
179
|
+
const c = await client.getContactById(contactId);
|
|
180
|
+
return await c.getProfilePicUrl();
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get the "about" text of a contact.
|
|
188
|
+
* Returns null if privacy settings block access.
|
|
189
|
+
* @param {string} contactId
|
|
190
|
+
* @returns {Promise<string|null>}
|
|
191
|
+
*/
|
|
192
|
+
async getAbout(contactId) {
|
|
193
|
+
try {
|
|
194
|
+
const c = await client.getContactById(contactId);
|
|
195
|
+
return await c.getAbout();
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
139
203
|
// ── Internal media helpers ───────────────────────────────────────────────────
|
|
140
204
|
|
|
141
205
|
function mediaFromSource(source, mimetype = "image/webp") {
|
|
@@ -151,8 +215,8 @@ function mediaFromSource(source, mimetype = "image/webp") {
|
|
|
151
215
|
*/
|
|
152
216
|
function makeSender(target) {
|
|
153
217
|
return {
|
|
154
|
-
async text(content) {
|
|
155
|
-
return target.sendMessage(content);
|
|
218
|
+
async text(content, opts = {}) {
|
|
219
|
+
return target.sendMessage(content, opts);
|
|
156
220
|
},
|
|
157
221
|
async image(filePath, caption = "") {
|
|
158
222
|
const media = MessageMedia.fromFilePath(filePath);
|
|
@@ -185,7 +249,7 @@ function chatIdTarget(client, chatId) {
|
|
|
185
249
|
/** Send to a specific chat by ID. Available in both setup and runtime. */
|
|
186
250
|
function buildSendToApi(client) {
|
|
187
251
|
return {
|
|
188
|
-
sendTo: (chatId, text)
|
|
252
|
+
sendTo: (chatId, text, opts) => client.sendMessage(chatId, text, opts),
|
|
189
253
|
sendImageTo: (chatId, filePath, caption) => makeSender(chatIdTarget(client, chatId)).image(filePath, caption),
|
|
190
254
|
sendVideoTo: (chatId, filePath, caption) => makeSender(chatIdTarget(client, chatId)).video(filePath, caption),
|
|
191
255
|
sendAudioTo: (chatId, filePath) => makeSender(chatIdTarget(client, chatId)).audio(filePath),
|
|
@@ -197,7 +261,7 @@ function buildSendToApi(client) {
|
|
|
197
261
|
function buildSendApi(chat) {
|
|
198
262
|
const sender = makeSender(chat);
|
|
199
263
|
return {
|
|
200
|
-
send: (text)
|
|
264
|
+
send: (text, opts) => sender.text(text, opts),
|
|
201
265
|
sendImage: (filePath, caption) => sender.image(filePath, caption),
|
|
202
266
|
sendVideo: (filePath, caption) => sender.video(filePath, caption),
|
|
203
267
|
sendAudio: (filePath) => sender.audio(filePath),
|
|
@@ -217,6 +281,7 @@ function buildBaseApi(client, pluginRegistry) {
|
|
|
217
281
|
download: buildDownloadApi(),
|
|
218
282
|
plugins: buildPluginsApi(pluginRegistry),
|
|
219
283
|
botId: client.info?.wid?._serialized ?? null,
|
|
284
|
+
contacts: buildContactsApi(),
|
|
220
285
|
...buildSendToApi(client),
|
|
221
286
|
};
|
|
222
287
|
}
|
|
@@ -294,6 +359,10 @@ export function buildApi({ msg, chat, client, pluginRegistry }) {
|
|
|
294
359
|
async react(emoji) {
|
|
295
360
|
return msg.react(emoji);
|
|
296
361
|
},
|
|
362
|
+
|
|
363
|
+
async getContact() {
|
|
364
|
+
return msg.getContact();
|
|
365
|
+
},
|
|
297
366
|
},
|
|
298
367
|
|
|
299
368
|
// ── chat ─────────────────────────────────────────────────
|
package/src/locales/en.json
CHANGED
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
"errors": {
|
|
60
60
|
"pluginLoad": "Failed to load plugin",
|
|
61
61
|
"messageProcess": "Failed to process message",
|
|
62
|
-
"stack": "Stack"
|
|
62
|
+
"stack": "Stack",
|
|
63
|
+
"chromeNotFound": "Chrome not found. Downloading...",
|
|
64
|
+
"couldNotDownloadChrome": "Could not download Chrome: "
|
|
63
65
|
}
|
|
64
66
|
}
|
package/src/locales/es.json
CHANGED
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"errors": {
|
|
55
55
|
"pluginLoad": "Error al cargar el plugin",
|
|
56
56
|
"messageProcess": "Error al procesar el mensaje",
|
|
57
|
-
"stack": "Stack"
|
|
57
|
+
"stack": "Stack",
|
|
58
|
+
"chromeNotFound": "No se ha encontrado Chrome. Descargando...",
|
|
59
|
+
"couldNotDownloadChrome": "No se ha podido descargar Chrome: "
|
|
58
60
|
}
|
|
59
61
|
}
|
package/src/locales/pt.json
CHANGED
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
"errors": {
|
|
60
60
|
"pluginLoad": "Falha ao carregar plugin",
|
|
61
61
|
"messageProcess": "Falha ao processar mensagem",
|
|
62
|
-
"stack": "Stack"
|
|
62
|
+
"stack": "Stack",
|
|
63
|
+
"chromeNotFound": "Chrome não encontrado. Baixando...",
|
|
64
|
+
"couldNotDownloadChrome": "Não foi possível baixar o Chrome: "
|
|
63
65
|
}
|
|
64
66
|
}
|