@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.
@@ -0,0 +1,177 @@
1
+ /**
2
+ * CLI utility to discover chat/group IDs.
3
+ *
4
+ * Usage:
5
+ * node get_id.js groups|contacts – list all groups or contacts
6
+ * node get_id.js <term> [term2] ... – search by name, number or ID fragment
7
+ * node get_id.js <term> --json – export results as JSON
8
+ * node get_id.js <term> --csv – export results as CSV
9
+ *
10
+ * Search matches against: display name, phone number, serialized ID.
11
+ */
12
+
13
+ import pkg from "whatsapp-web.js";
14
+ import qrcode from "qrcode-terminal";
15
+ import fs from "fs";
16
+ import { resolvePuppeteerConfig } from "#client/environment";
17
+
18
+ const CLIENT_ID = "getId";
19
+ const { Client, LocalAuth } = pkg;
20
+
21
+ // ── Parse args ────────────────────────────────────────────────────────────────
22
+ const rawArgs = process.argv.slice(2);
23
+ const exportJson = rawArgs.includes("--json");
24
+ const exportCsv = rawArgs.includes("--csv");
25
+ const terms = rawArgs.filter(a => !a.startsWith("--")).map(a => a.toLowerCase());
26
+
27
+ if (!terms.length) {
28
+ console.log([
29
+ "Usage:",
30
+ " node get_id.js me – show your own number and ID",
31
+ " node get_id.js groups|contacts",
32
+ " node get_id.js <term> [term2] ... – search name, number or ID fragment",
33
+ " Add --json or --csv to export results",
34
+ ].join("\n"));
35
+ process.exit(0);
36
+ }
37
+
38
+ // ── Client ────────────────────────────────────────────────────────────────────
39
+ const client = new Client({
40
+ authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
41
+ puppeteer: {
42
+ headless: true,
43
+ args: ["--no-sandbox", "--disable-setuid-sandbox", ...(resolvePuppeteerConfig().args || [])],
44
+ ...resolvePuppeteerConfig(),
45
+ },
46
+ });
47
+
48
+ client.on("qr", (qr) => {
49
+ console.log("[QR] Scan to authenticate:");
50
+ qrcode.generate(qr, { small: true });
51
+ });
52
+
53
+ // ── Helpers ───────────────────────────────────────────────────────────────────
54
+ function matches(chat, term) {
55
+ const name = (chat.name || "").toLowerCase();
56
+ const number = (chat.id?.user || "").toLowerCase();
57
+ const serial = (chat.id?._serialized || "").toLowerCase();
58
+ return name.includes(term) || number.includes(term) || serial.includes(term);
59
+ }
60
+
61
+ function buildRow(chat) {
62
+ return {
63
+ name: chat.name || chat.id?.user || "",
64
+ number: chat.id?.user || "",
65
+ id: chat.id?._serialized || "",
66
+ group: chat.isGroup,
67
+ };
68
+ }
69
+
70
+ function printTable(rows) {
71
+ const LINE = "─".repeat(48);
72
+ rows.forEach(r => {
73
+ console.log(LINE);
74
+ console.log("Name: ", r.name);
75
+ console.log("Number:", r.number);
76
+ console.log("ID: ", r.id);
77
+ if (r.phone) console.log("Phone: ", r.phone);
78
+ console.log("Group: ", r.group);
79
+ });
80
+ console.log(LINE);
81
+ console.log(`\n${rows.length} result(s) found.`);
82
+ }
83
+
84
+ function exportResults(rows) {
85
+ if (exportJson) {
86
+ const file = "get_id_results.json";
87
+ fs.writeFileSync(file, JSON.stringify(rows, null, 2));
88
+ console.log(`\nExported to ${file}`);
89
+ }
90
+ if (exportCsv) {
91
+ const file = "get_id_results.csv";
92
+ const header = "name,number,id,group";
93
+ const lines = rows.map(r =>
94
+ [r.name, r.number, r.id, r.group].map(v => `"${String(v).replace(/"/g, '""')}"`).join(",")
95
+ );
96
+ fs.writeFileSync(file, [header, ...lines].join("\n"));
97
+ console.log(`Exported to ${file}`);
98
+ }
99
+ }
100
+
101
+ // ── Main ──────────────────────────────────────────────────────────────────────
102
+ client.on("ready", async () => {
103
+ console.log("[OK] Connected. Searching…\n");
104
+
105
+ const [first, ...rest] = terms;
106
+
107
+ if (first === "me") {
108
+ const info = client.info;
109
+
110
+ // Attempt to resolve own LID via internal Store (may be unavailable on older WA versions)
111
+ let lid = null;
112
+ try {
113
+ const serialized = client.info.wid._serialized;
114
+ lid = await client.pupPage.evaluate((s) => {
115
+ const wid = window.Store.WidFactory.createWid(s);
116
+ const resolved = window.Store.LidUtils.getCurrentLid(wid);
117
+ return resolved?._serialized ?? null;
118
+ }, serialized);
119
+ } catch (_) { /* LID unavailable */ }
120
+
121
+ const LINE = "─".repeat(48);
122
+ console.log(LINE);
123
+ console.log("Name: ", info.pushname || "(no name)");
124
+ console.log("Number:", info.wid?.user || "");
125
+ console.log("ID: ", info.wid?._serialized || "");
126
+ if (lid) console.log("LID: ", lid);
127
+ console.log(LINE);
128
+ await client.destroy();
129
+ process.exit(0);
130
+ }
131
+
132
+ const chats = await client.getChats();
133
+
134
+ let filtered;
135
+ if (first === "groups") {
136
+ filtered = chats.filter(c => c.isGroup);
137
+ } else if (first === "contacts") {
138
+ filtered = chats.filter(c => !c.isGroup);
139
+ } else {
140
+ // All terms must match (AND logic across multiple terms)
141
+ filtered = chats.filter(c => terms.every(t => matches(c, t)));
142
+ }
143
+
144
+ if (!filtered.length) {
145
+ console.log("No results found.");
146
+ } else {
147
+ const rows = filtered.map(buildRow);
148
+
149
+ // For contacts with @lid, resolve the @c.us phone ID in bulk
150
+ const lidRows = rows.filter(r => !r.group && r.id.endsWith("@lid"));
151
+ if (lidRows.length) {
152
+ try {
153
+ const serializedIds = lidRows.map(r => r.id);
154
+ const phoneMap = await client.pupPage.evaluate((ids) => {
155
+ const result = {};
156
+ for (const id of ids) {
157
+ try {
158
+ const wid = window.Store.WidFactory.createWid(id);
159
+ const phone = window.Store.LidUtils.getPhoneNumber(wid);
160
+ result[id] = phone?._serialized ?? null;
161
+ } catch (_) { result[id] = null; }
162
+ }
163
+ return result;
164
+ }, serializedIds);
165
+ for (const row of lidRows) row.phone = phoneMap[row.id] || null;
166
+ } catch (_) { /* LID resolution unavailable */ }
167
+ }
168
+
169
+ printTable(rows);
170
+ exportResults(rows);
171
+ }
172
+
173
+ await client.destroy();
174
+ process.exit(0);
175
+ });
176
+
177
+ client.initialize();
@@ -0,0 +1,129 @@
1
+ /**
2
+ * src/utils/pluginI18n.js
3
+ *
4
+ * Independent i18n system for plugins.
5
+ * Plugins load their own translations from locale/ folder.
6
+ * Completely separate from bot core i18n.
7
+ *
8
+ * Usage in plugin:
9
+ * import { createPluginI18n } from "../utils/pluginI18n.js";
10
+ * const { t } = createPluginI18n(import.meta.url);
11
+ *
12
+ * Folder structure:
13
+ * myPlugin/
14
+ * index.js
15
+ * locale/
16
+ * en.json (required - fallback)
17
+ * pt.json
18
+ * es.json
19
+ */
20
+
21
+ import fs from "fs";
22
+ import path from "path";
23
+ import { fileURLToPath } from "url";
24
+ import { LANGUAGE } from "#config";
25
+
26
+ // Default/fallback language
27
+ const DEFAULT_LANG = "en";
28
+
29
+ /**
30
+ * Gets a nested value from an object using dot path
31
+ * @param {object} obj
32
+ * @param {string} key - path like "error.notFound"
33
+ * @returns {string|undefined}
34
+ */
35
+ function getNestedValue(obj, key) {
36
+ const parts = key.split(".");
37
+ let current = obj;
38
+
39
+ for (const part of parts) {
40
+ if (current === null || current === undefined || typeof current !== "object") {
41
+ return undefined;
42
+ }
43
+ current = current[part];
44
+ }
45
+
46
+ return current;
47
+ }
48
+
49
+ /**
50
+ * Replaces placeholders {{key}} with values from context
51
+ * @param {string} str
52
+ * @param {object} context
53
+ * @returns {string}
54
+ */
55
+ function interpolate(str, context = {}) {
56
+ return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
57
+ return context[key] !== undefined ? String(context[key]) : match;
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Load translations for a plugin
63
+ * @param {string} localeDir - path to plugin's locale folder
64
+ * @param {string} lang - target language
65
+ * @returns {{ translations: object, fallback: object }}
66
+ */
67
+ function loadTranslations(localeDir, lang) {
68
+ let translations = {};
69
+ let fallback = {};
70
+
71
+ try {
72
+ const targetPath = path.join(localeDir, `${lang}.json`);
73
+ if (fs.existsSync(targetPath)) {
74
+ translations = JSON.parse(fs.readFileSync(targetPath, "utf8"));
75
+ }
76
+
77
+ const fallbackPath = path.join(localeDir, `${DEFAULT_LANG}.json`);
78
+ if (fs.existsSync(fallbackPath)) {
79
+ fallback = JSON.parse(fs.readFileSync(fallbackPath, "utf8"));
80
+ }
81
+ } catch {
82
+ // Silent fail - plugin may not have translations
83
+ }
84
+
85
+ return { translations, fallback };
86
+ }
87
+
88
+ /**
89
+ * Creates an isolated translation function for a plugin.
90
+ * Language priority: PLUGIN_LANG env var > manybot.conf LANGUAGE > en
91
+ *
92
+ * @param {string} pluginMetaUrl - import.meta.url from the plugin
93
+ * @returns {{ t: Function, lang: string }}
94
+ */
95
+ export function createPluginI18n(pluginMetaUrl) {
96
+ const pluginDir = path.dirname(fileURLToPath(pluginMetaUrl));
97
+ const localeDir = path.join(pluginDir, "locale");
98
+
99
+ const targetLang =
100
+ process.env.PLUGIN_LANG?.trim().toLowerCase() ||
101
+ LANGUAGE?.trim().toLowerCase() ||
102
+ DEFAULT_LANG;
103
+
104
+ const { translations, fallback } = loadTranslations(localeDir, targetLang);
105
+
106
+ /**
107
+ * Translation function
108
+ * @param {string} key - translation key (e.g., "error.notFound")
109
+ * @param {object} context - values to interpolate {{key}}
110
+ * @returns {string}
111
+ */
112
+ function t(key, context = {}) {
113
+ let value = getNestedValue(translations, key);
114
+
115
+ if (value === undefined) {
116
+ value = getNestedValue(fallback, key);
117
+ }
118
+
119
+ if (value === undefined) return key;
120
+
121
+ if (typeof value !== "string") return String(value);
122
+
123
+ return interpolate(value, context);
124
+ }
125
+
126
+ return { t, lang: targetLang };
127
+ }
128
+
129
+ export default { createPluginI18n };