@manybot/manybot 5.7.0 → 5.9.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.
Files changed (81) hide show
  1. package/README.md +28 -3
  2. package/dist/client/banner.js +10 -0
  3. package/dist/client/banner.test.js +31 -0
  4. package/dist/client/store.js +91 -6
  5. package/dist/client/store.test.js +170 -0
  6. package/dist/config.js +28 -44
  7. package/dist/config.test.js +26 -0
  8. package/dist/download/queue.js +13 -4
  9. package/dist/drivers/baileys/adapter.js +133 -15
  10. package/dist/drivers/baileys/api/contacts.integration.test.js +261 -0
  11. package/dist/drivers/baileys/api/groupMeta.test.js +235 -0
  12. package/dist/drivers/baileys/api/index.js +384 -62
  13. package/dist/drivers/baileys/index.js +92 -36
  14. package/dist/drivers/baileys/loginPrompt.js +0 -2
  15. package/dist/drivers/baileys/messageHandler.js +344 -4
  16. package/dist/drivers/baileys/messageHandler.test.js +445 -0
  17. package/dist/drivers/baileysAdapter.test.js +378 -0
  18. package/dist/drivers/jid.js +26 -0
  19. package/dist/drivers/jid.test.js +74 -0
  20. package/dist/drivers/types.js +5 -5
  21. package/dist/i18n/index.js +20 -24
  22. package/dist/kernel/activeDriverSend.js +21 -0
  23. package/dist/kernel/activeDriverSend.test.js +89 -0
  24. package/dist/kernel/alerts.js +3 -9
  25. package/dist/kernel/chatOverrides.js +46 -0
  26. package/dist/kernel/chatOverrides.test.js +59 -0
  27. package/dist/kernel/chatSession.js +65 -0
  28. package/dist/kernel/chatSession.test.js +46 -0
  29. package/dist/kernel/commandAccess.js +66 -0
  30. package/dist/kernel/commandAccess.test.js +74 -0
  31. package/dist/kernel/commandDeprecation.js +170 -0
  32. package/dist/kernel/commandDeprecation.test.js +114 -0
  33. package/dist/kernel/commandMenu.js +357 -0
  34. package/dist/kernel/commandMenu.test.js +363 -0
  35. package/dist/kernel/commandPermissions.js +171 -0
  36. package/dist/kernel/commandPermissions.test.js +227 -0
  37. package/dist/kernel/commandRegistry.js +583 -0
  38. package/dist/kernel/commandRegistry.test.js +158 -0
  39. package/dist/kernel/commandsConfig.js +949 -0
  40. package/dist/kernel/commandsConfig.test.js +482 -0
  41. package/dist/kernel/contactAutoSave.js +6 -6
  42. package/dist/kernel/contactAutoSave.test.js +87 -0
  43. package/dist/kernel/coreCommands.js +62 -0
  44. package/dist/kernel/driverManager.js +10 -6
  45. package/dist/kernel/driverManager.test.js +90 -0
  46. package/dist/kernel/integrationMode.js +88 -0
  47. package/dist/kernel/integrationMode.test.js +95 -0
  48. package/dist/kernel/loadIntegrationPlugin.test.js +67 -0
  49. package/dist/kernel/pluginApi.test.js +600 -0
  50. package/dist/kernel/pluginGuard.js +18 -13
  51. package/dist/kernel/pluginGuard.test.js +39 -0
  52. package/dist/kernel/pluginLoader.js +169 -11
  53. package/dist/kernel/pluginLoader.test.js +190 -0
  54. package/dist/kernel/runCommand.js +284 -0
  55. package/dist/kernel/runCommand.test.js +497 -0
  56. package/dist/kernel/sendFallbackGuard.js +19 -48
  57. package/dist/kernel/sendFallbackGuard.test.js +80 -0
  58. package/dist/kernel/sendGuard.js +38 -42
  59. package/dist/kernel/sendGuard.test.js +102 -0
  60. package/dist/kernel/settingsDb.js +19 -5
  61. package/dist/kernel/statusServer.js +9 -2
  62. package/dist/kernel/statusServer.test.js +70 -0
  63. package/dist/kernel/testConfig.js +192 -0
  64. package/dist/kernel/testConfig.test.js +181 -0
  65. package/dist/kernel/updateCheck.js +33 -10
  66. package/dist/locales/en.json +77 -13
  67. package/dist/locales/es.json +77 -13
  68. package/dist/locales/pt.json +77 -13
  69. package/dist/logger/logger.js +23 -3
  70. package/dist/logger/logger.test.js +45 -0
  71. package/dist/main.js +5 -76
  72. package/dist/plugins/__manybot_integration__/index.js +184 -0
  73. package/dist/plugins/__manybot_integration__/index.test.js +218 -0
  74. package/dist/utils/phoneNumber.js +83 -0
  75. package/dist/utils/phoneNumber.test.js +53 -0
  76. package/package.json +76 -18
  77. package/dist/drivers/whatsmeow/client.js +0 -252
  78. package/dist/drivers/whatsmeow/index.js +0 -79
  79. package/dist/drivers/whatsmeow/installer.js +0 -86
  80. package/dist/drivers/whatsmeow/supervisor.js +0 -328
  81. package/dist/drivers/whatsmeow/whatsmeow.proto +0 -64
@@ -0,0 +1,181 @@
1
+ import test, { describe, before, after, beforeEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "fs/promises";
4
+ import os from "os";
5
+ import path from "path";
6
+ const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "manybot-test-config-"));
7
+ process.env.MANYBOT_CONFIG_DIR = configDir;
8
+ const { getTestConfig, requireTestConfig, normalizeTestChat, _resetTestConfigForTests, TEST_CHAT_ENV, RUN_WHATSAPP_TESTS_ENV, TEST_CHAT_TOML_KEY, } = await import("#kernel/testConfig.js");
9
+ const { TOML_CONFIG_FILE } = await import("#config");
10
+ const tomlPath = path.join(configDir, "manybot.toml");
11
+ async function writeToml(contents) {
12
+ await fs.writeFile(tomlPath, contents, "utf8");
13
+ }
14
+ function clearEnv() {
15
+ delete process.env[TEST_CHAT_ENV];
16
+ delete process.env[RUN_WHATSAPP_TESTS_ENV];
17
+ }
18
+ before(async () => {
19
+ await fs.writeFile(tomlPath, "", "utf8");
20
+ });
21
+ beforeEach(async () => {
22
+ clearEnv();
23
+ await writeToml("");
24
+ _resetTestConfigForTests();
25
+ });
26
+ after(async () => {
27
+ clearEnv();
28
+ await fs.rm(configDir, { recursive: true, force: true });
29
+ });
30
+ describe("kernel/testConfig — normalizeTestChat", () => {
31
+ test("accepts a bare number and normalizes to @s.whatsapp.net", () => {
32
+ assert.equal(normalizeTestChat("5516999999999"), "5516999999999@s.whatsapp.net");
33
+ });
34
+ test("accepts a leading + on bare numbers", () => {
35
+ assert.equal(normalizeTestChat("+5516999999999"), "5516999999999@s.whatsapp.net");
36
+ });
37
+ test("preserves @c.us", () => {
38
+ assert.equal(normalizeTestChat("5516999999999@c.us"), "5516999999999@c.us");
39
+ });
40
+ test("preserves @s.whatsapp.net", () => {
41
+ assert.equal(normalizeTestChat("5516999999999@s.whatsapp.net"), "5516999999999@s.whatsapp.net");
42
+ });
43
+ test("preserves @lid", () => {
44
+ assert.equal(normalizeTestChat("12345@lid"), "12345@lid");
45
+ });
46
+ test("preserves @g.us", () => {
47
+ assert.equal(normalizeTestChat("120363012345678@g.us"), "120363012345678@g.us");
48
+ });
49
+ test("trims surrounding whitespace", () => {
50
+ assert.equal(normalizeTestChat(" 5516999999999 "), "5516999999999@s.whatsapp.net");
51
+ });
52
+ test("rejects empty string", () => {
53
+ assert.throws(() => normalizeTestChat(""), /empty/);
54
+ });
55
+ test("rejects non-string input", () => {
56
+ assert.throws(() => normalizeTestChat(undefined), /string/);
57
+ });
58
+ test("rejects unknown suffix", () => {
59
+ assert.throws(() => normalizeTestChat("foo@example.com"), /JID/);
60
+ });
61
+ test("rejects JID with bad chars in the local part", () => {
62
+ assert.throws(() => normalizeTestChat("5516!@c.us"), /local part/);
63
+ });
64
+ });
65
+ describe("kernel/testConfig — getTestConfig", () => {
66
+ test("returns null chat and skipReason when nothing is configured", async () => {
67
+ const cfg = await getTestConfig();
68
+ assert.equal(cfg.chat, null);
69
+ assert.equal(cfg.source, null);
70
+ assert.equal(cfg.runWhatsApp, false);
71
+ assert.match(cfg.skipReason, /TEST_CHAT is not set/);
72
+ });
73
+ test("env wins over TOML", async () => {
74
+ await writeToml(`${TEST_CHAT_TOML_KEY} = "5516000000001"\n`);
75
+ process.env[TEST_CHAT_ENV] = "5516000000002";
76
+ const cfg = await getTestConfig();
77
+ assert.equal(cfg.chat, "5516000000002@s.whatsapp.net");
78
+ assert.equal(cfg.source, "env");
79
+ });
80
+ test("falls back to TOML when env is unset", async () => {
81
+ await writeToml(`${TEST_CHAT_TOML_KEY} = "5516000000003"\n`);
82
+ const cfg = await getTestConfig();
83
+ assert.equal(cfg.chat, "5516000000003@s.whatsapp.net");
84
+ assert.equal(cfg.source, "toml");
85
+ });
86
+ test("empty string in TOML is treated as absent", async () => {
87
+ await writeToml(`${TEST_CHAT_TOML_KEY} = ""\n`);
88
+ const cfg = await getTestConfig();
89
+ assert.equal(cfg.chat, null);
90
+ });
91
+ test("whitespace-only env is treated as absent", async () => {
92
+ process.env[TEST_CHAT_ENV] = " ";
93
+ const cfg = await getTestConfig();
94
+ assert.equal(cfg.chat, null);
95
+ });
96
+ test("invalid value in TOML is ignored (warning logged, not thrown)", async () => {
97
+ await writeToml(`${TEST_CHAT_TOML_KEY} = "not-a-valid-jid@x.com"\n`);
98
+ const cfg = await getTestConfig();
99
+ assert.equal(cfg.chat, null);
100
+ });
101
+ test("non-string TOML value is ignored", async () => {
102
+ await writeToml(`${TEST_CHAT_TOML_KEY} = 42\n`);
103
+ const cfg = await getTestConfig();
104
+ assert.equal(cfg.chat, null);
105
+ });
106
+ test("runWhatsApp is true only when env equals '1'", async () => {
107
+ process.env[TEST_CHAT_ENV] = "5516000000004";
108
+ process.env[RUN_WHATSAPP_TESTS_ENV] = "1";
109
+ const on = await getTestConfig();
110
+ assert.equal(on.runWhatsApp, true);
111
+ _resetTestConfigForTests();
112
+ process.env[RUN_WHATSAPP_TESTS_ENV] = "true";
113
+ const off1 = await getTestConfig();
114
+ assert.equal(off1.runWhatsApp, false);
115
+ _resetTestConfigForTests();
116
+ process.env[RUN_WHATSAPP_TESTS_ENV] = "0";
117
+ const off2 = await getTestConfig();
118
+ assert.equal(off2.runWhatsApp, false);
119
+ _resetTestConfigForTests();
120
+ delete process.env[RUN_WHATSAPP_TESTS_ENV];
121
+ const off3 = await getTestConfig();
122
+ assert.equal(off3.runWhatsApp, false);
123
+ });
124
+ test("skipReason explains the missing opt-in even when TEST_CHAT is set", async () => {
125
+ await writeToml(`${TEST_CHAT_TOML_KEY} = "5516000000005"\n`);
126
+ const cfg = await getTestConfig();
127
+ assert.equal(cfg.chat, "5516000000005@s.whatsapp.net");
128
+ assert.equal(cfg.runWhatsApp, false);
129
+ assert.match(cfg.skipReason, /MANYBOT_RUN_WHATSAPP_TESTS=1/);
130
+ });
131
+ test("skipReason is null when everything is configured", async () => {
132
+ process.env[TEST_CHAT_ENV] = "5516000000006";
133
+ process.env[RUN_WHATSAPP_TESTS_ENV] = "1";
134
+ const cfg = await getTestConfig();
135
+ assert.equal(cfg.skipReason, null);
136
+ });
137
+ test("result is cached across calls within the same process", async () => {
138
+ process.env[TEST_CHAT_ENV] = "5516000000007";
139
+ const first = await getTestConfig();
140
+ process.env[TEST_CHAT_ENV] = "5516000000008";
141
+ const second = await getTestConfig();
142
+ assert.equal(first.chat, second.chat, "cache must not re-read env");
143
+ _resetTestConfigForTests();
144
+ const third = await getTestConfig();
145
+ assert.equal(third.chat, "5516000000008@s.whatsapp.net");
146
+ });
147
+ test("missing manybot.toml is treated as 'no value'", async () => {
148
+ await fs.rm(tomlPath, { force: true });
149
+ const cfg = await getTestConfig();
150
+ assert.equal(cfg.chat, null);
151
+ assert.equal(cfg.source, null);
152
+ });
153
+ test("malformed TOML is logged and treated as 'no value'", async () => {
154
+ await fs.writeFile(tomlPath, "this is not valid = = toml =", "utf8");
155
+ const cfg = await getTestConfig();
156
+ assert.equal(cfg.chat, null);
157
+ });
158
+ });
159
+ describe("kernel/testConfig — requireTestConfig", () => {
160
+ test("returns config when fully configured", async () => {
161
+ process.env[TEST_CHAT_ENV] = "5516000000009";
162
+ process.env[RUN_WHATSAPP_TESTS_ENV] = "1";
163
+ const cfg = await requireTestConfig();
164
+ assert.equal(cfg.chat, "5516000000009@s.whatsapp.net");
165
+ assert.equal(cfg.runWhatsApp, true);
166
+ });
167
+ test("throws when TEST_CHAT is missing", async () => {
168
+ process.env[RUN_WHATSAPP_TESTS_ENV] = "1";
169
+ await assert.rejects(requireTestConfig(), /TEST_CHAT is not set/);
170
+ });
171
+ test("throws when opt-in is missing", async () => {
172
+ process.env[TEST_CHAT_ENV] = "5516000000010";
173
+ await assert.rejects(requireTestConfig(), /MANYBOT_RUN_WHATSAPP_TESTS=1/);
174
+ });
175
+ });
176
+ // smoke: TOML_CONFIG_FILE should point inside the temp config dir
177
+ describe("kernel/testConfig — wiring", () => {
178
+ test("TOML_CONFIG_FILE respects MANYBOT_CONFIG_DIR", () => {
179
+ assert.equal(TOML_CONFIG_FILE, tomlPath);
180
+ });
181
+ });
@@ -2,13 +2,19 @@
2
2
  * updateCheck.ts
3
3
  *
4
4
  * Compares the locally installed manybot version against the latest
5
- * published on npm, and fires an "info" alert (via alerts.ts) when a
5
+ * GitHub Release and fires an "info" alert (via alerts.ts) when a
6
6
  * newer version is available. Runs once on startup and then on a
7
7
  * schedule — both configurable (UPDATE_CHECK_ENABLED,
8
8
  * UPDATE_CHECK_INTERVAL_HOURS).
9
9
  *
10
- * Never throws a failed check (offline, npm down) is logged at debug
11
- * level and silently skipped; it'll just try again next cycle.
10
+ * GitHub Releases is the source of truth for distribution: release
11
+ * candidate tags (-rc.N) skip npm entirely, and stable releases stay
12
+ * in "staged" on npm until a manual 2FA approval lands, so reading
13
+ * registry.npmjs.org here would either miss an already-published
14
+ * version or notify about one only after a long delay.
15
+ *
16
+ * Never throws — a failed check (offline, GitHub down) is logged at
17
+ * debug level and silently skipped; it'll just try again next cycle.
12
18
  */
13
19
  import { readFileSync } from "fs";
14
20
  import { fileURLToPath } from "url";
@@ -16,10 +22,11 @@ import path from "path";
16
22
  import { UPDATE_CHECK_ENABLED, UPDATE_CHECK_INTERVAL_HOURS } from "#config";
17
23
  import { sendAlert } from "#kernel/alerts.js";
18
24
  import { logger } from "#logger";
25
+ import { t } from "#i18n";
19
26
  const __filename = fileURLToPath(import.meta.url);
20
27
  const __dirname = path.dirname(__filename);
21
28
  const pkg = JSON.parse(readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
22
- const REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(pkg.name)}/latest`;
29
+ const GITHUB_RELEASES_LATEST = "https://api.github.com/repos/many-bot/manybot/releases/latest";
23
30
  /** Naive semver compare — good enough for x.y.z, no pre-release handling. */
24
31
  function isNewer(latest, current) {
25
32
  const a = latest.split(".").map(Number);
@@ -34,6 +41,14 @@ function isNewer(latest, current) {
34
41
  }
35
42
  return false;
36
43
  }
44
+ /**
45
+ * GitHub tags come prefixed with "v" (e.g. "v5.7.0"). Strip it so we
46
+ * can compare against pkg.version directly and render the upgrade
47
+ * command without the prefix.
48
+ */
49
+ function stripTagPrefix(tag) {
50
+ return tag.startsWith("v") ? tag.slice(1) : tag;
51
+ }
37
52
  let alreadyNotifiedFor = null;
38
53
  /**
39
54
  * Runs a single check. Safe to call anytime (startup, interval, manual
@@ -43,23 +58,31 @@ export async function checkForUpdate() {
43
58
  if (!UPDATE_CHECK_ENABLED)
44
59
  return;
45
60
  try {
46
- const res = await fetch(REGISTRY_URL);
61
+ const res = await fetch(GITHUB_RELEASES_LATEST);
47
62
  if (!res.ok) {
48
- logger.debug(`[updateCheck] npm registry responded ${res.status}`);
63
+ logger.debug(`[updateCheck] GitHub Releases responded ${res.status}`);
49
64
  return;
50
65
  }
51
66
  const data = await res.json();
52
- const latest = data.version;
53
- if (!latest || !isNewer(latest, pkg.version))
67
+ const rawTag = data.tag_name;
68
+ if (!rawTag)
69
+ return;
70
+ const latest = stripTagPrefix(rawTag);
71
+ if (!isNewer(latest, pkg.version))
54
72
  return;
55
73
  // Don't re-alert every cycle for the same version once already notified.
56
74
  if (alreadyNotifiedFor === latest)
57
75
  return;
58
76
  alreadyNotifiedFor = latest;
77
+ const releaseUrl = `https://github.com/many-bot/manybot/releases/tag/${rawTag}`;
59
78
  await sendAlert({
60
79
  level: "info",
61
- title: "Nova versão do manybot disponível",
62
- message: `Instalada: ${pkg.version} → disponível: ${latest}. Rode "npm install -g ${pkg.name}@${latest}" (ou equivalente) para atualizar.`,
80
+ title: t("alerts.updateAvailableTitle"),
81
+ message: t("alerts.updateAvailableMessage", {
82
+ installed: pkg.version,
83
+ available: latest,
84
+ url: releaseUrl,
85
+ }),
63
86
  });
64
87
  }
65
88
  catch (e) {
@@ -30,19 +30,94 @@
30
30
  "schedulerError": "Plugin \"{{name}}\" scheduling error: {{message}}",
31
31
  "schedulerRegistered": "Schedule registered — plugin \"{{name}}\" → \"{{expression}}\"",
32
32
  "downloadJobFailed": "Download job failed — {{message}}",
33
+ "downloadJobNoErrorFn": "Download job failed and no errorFn was provided to enqueue()",
33
34
  "reconnecting": "Reconnecting in {{secs}}s...",
34
35
  "reconnectHalted": "Gave up reconnecting after {{attempts}} attempts — call connect() manually to retry. Repeated reconnect loops can make a WhatsApp restriction last longer.",
35
36
  "sessionExpired": "Session expired or logged out — removing local session and restarting...",
36
37
  "cacheLoaded": "{{count}} chat(s) loaded from cache.",
37
38
  "cacheLoadedStale": "{{count}} chat(s) loaded from cache (stale).",
38
39
  "sendFailedNoFallback": "manybot: no fallback driver available (jid={{jid}}, primary={{driver}})",
39
- "sendFailedBothDrivers": "manybot: send failed on both drivers (jid={{jid}}, tried={{primary}} then {{secondary}})"
40
+ "sendFailedBothDrivers": "manybot: send failed on both drivers (jid={{jid}}, tried={{primary}} then {{secondary}})",
41
+ "commandsConfigMissingCmd": "commands.yaml: command \"{{id}}\" is missing required field \"cmd\" — skipped",
42
+ "commandRegistryInvalidPluginCommand": "commandRegistry: plugin \"{{plugin}}\" command \"{{function}}\" ({{id}}) has an invalid or missing \"cmd\" — skipped",
43
+ "commandsConfigArgumentMissingName": "commands.yaml: argument entry under \"{{id}}\" is missing required field \"name\" — skipped",
44
+ "commandsConfigArgumentMissingType": "commands.yaml: argument \"{{name}}\" under \"{{id}}\" is missing required field \"type\" — skipped",
45
+ "commandsConfigUnknownArgType": "commands.yaml: argument \"{{name}}\" under \"{{id}}\" has unknown type \"{{type}}\" — skipped",
46
+ "commandsConfigChoiceArgumentNoChoices": "commands.yaml: argument \"{{name}}\" under \"{{id}}\" is type \"choice\" but declared no choices — falling back to free placeholder in usage",
47
+ "commandsConfigArgumentsNotList": "commands.yaml: arguments under \"{{id}}\" must be a list — ignored",
48
+ "commandsConfigSubcommandMissingCmd": "commands.yaml: subcommand under \"{{id}}\" is missing required field \"cmd\" — skipped",
49
+ "commandsConfigSubcommandsNotList": "commands.yaml: subcommands under \"{{id}}\" must be a list — ignored",
50
+ "commandsConfigDuplicateSubcommand": "commands.yaml: duplicate subcommand \"{{cmd}}\" under \"{{id}}\" — keeping the first one",
51
+ "commandsConfigReadFailed": "commands.yaml: could not be read ({{path}}): {{message}}",
52
+ "commandsConfigParseFailed": "commands.yaml: YAML parse error: {{message}}",
53
+ "commandsConfigInvalidRoot": "commands.yaml: top-level must be a map of command entries",
54
+ "commandsConfigInvalidEntry": "commands.yaml: entry \"{{id}}\" is not an object — skipped",
55
+ "commandsConfigImportReadFailed": "commands.yaml: import \"{{path}}\" could not be read: {{message}}",
56
+ "commandsConfigImportParseFailed": "commands.yaml: import \"{{path}}\" YAML parse error: {{message}}",
57
+ "commandsConfigImportInvalidRoot": "commands.yaml: import \"{{path}}\" top-level must be a map of sections — skipped",
58
+ "commandsConfigImportNested": "commands.yaml: import \"{{path}}\" declares its own \"import\" — nested imports are not supported and were ignored",
59
+ "commandsConfigImportKeyConflict": "commands.yaml: import \"{{path}}\" redefines \"{{key}}\", already owned by \"{{owner}}\" — keeping the first one",
60
+ "commandsConfigCommandsWrapperInvalid": "commands.yaml: top-level \"commands:\" must be a map of command entries — ignored",
61
+ "commandsConfigCommandsWrapperCollision": "commands.yaml: \"commands:\" wrapper defines \"{{key}}\" that already exists at the top level — keeping the top-level entry",
62
+ "commandsConfigConflictingScopeFlags": "commands.yaml: \"permissions\" has both group_only and dm_only set to true — pick one (or use \"scope:\" directly)",
63
+ "commandsConfigLoadingPresetMissing": "commands.yaml: \"{{id}}\" loading preset \"{{name}}\" not found — leaving the field unset",
64
+ "commandsConfigLoadingInvalid": "commands.yaml: \"{{id}}\" loading value is not a preset name or inline object — ignored",
65
+ "commandsConfigLoadingUnknownType": "commands.yaml: \"{{id}}\" loading type \"{{type}}\" is not one of reaction|typing|recording_audio|spinner|none — ignored",
66
+ "commandsConfigLoadingUnknownProp": "commands.yaml: \"{{id}}\" loading type \"{{type}}\" does not accept property \"{{key}}\" — entry is malformed and was dropped",
67
+ "commandRegistryOrphanEntry": "commandRegistry: entry \"{{id}}\" targets {{plugin}}.{{function}} but no active plugin provides it — skipped",
68
+ "commandRegistryInvalidEntry": "commandRegistry: entry \"{{id}}\" has no plugin/function and no text — skipped",
69
+ "commandRegistryInvocationCollision": "commandRegistry: invocation \"{{text}}\" already owned by \"{{winner}}\" — dropping {{loser}} ({{via}})",
70
+ "commandDeprecationRenamed": "commandRegistry: command \"{{id}}\" renamed from \"{{old}}\" to \"{{new}}\" — notifying old callers for {{days}} days",
71
+ "commandDeprecationRemoved": "commandRegistry: command \"{{id}}\" (cmd \"{{old}}\") was removed — notifying old callers for {{days}} days",
72
+ "commandDeprecationFallbackRenamed": "Command \"{{old}}\" was renamed to \"{{new}}\". Please update your message.",
73
+ "commandDeprecationFallbackRemoved": "Command \"{{old}}\" is no longer available.",
74
+ "commandDeprecationReservedInvocation": "commandRegistry: invocation \"{{text}}\" is reserved by active deprecation for \"{{id}}\" — refusing registration",
75
+ "commandManualMissing": "No detailed manual available for command \"{{cmd}}\".",
76
+ "commandNotFound": "Command or category \"{{cmd}}\" not found. Use {{prefix}}{{menuCmd}} to view the command list.",
77
+ "commandNotFoundSuggestion": "Command \"{{cmd}}\" not found. Did you mean {{prefix}}{{suggestion}}? Use {{prefix}}{{menuCmd}} to view the command list.",
78
+ "commandRegistryMenuAliasCollision": "commandRegistry: menu alias \"{{alias}}\" collided with real command \"{{winner}}\" — menu alias ignored"
40
79
  },
41
80
  "errors": {
42
81
  "stack": "Stack",
43
82
  "invalid_config": "Invalid config file {{file}}: {{error}}",
44
83
  "fix_config": "Fix the syntax in {{file}} before starting the bot."
45
84
  },
85
+ "menu": {
86
+ "intro": "Use {prefix}<command> to run it or {prefix}help <command> to view its manual.",
87
+ "other": "Other",
88
+ "category": "Category",
89
+ "manual": "Manual",
90
+ "description": "Description"
91
+ },
92
+ "commandPermissions": {
93
+ "botNotAdmin": "This command requires the bot to be a group administrator.",
94
+ "senderNotAdmin": "This command is restricted to group administrators.",
95
+ "ownerOnly": "This command can only be run by the bot owner.",
96
+ "donoOnly": "This command can only be run by its designated owner.",
97
+ "wrongScope": "This command cannot be used in this context.",
98
+ "cooldown": "Wait {{seconds}}s before using this command again.",
99
+ "blacklist": "You are not allowed to use this command.",
100
+ "allowedChats": "This command is not available in this chat."
101
+ },
102
+ "commandRun": {
103
+ "missingRequiredArg": "Missing required argument(s).",
104
+ "unknownSubcommand": "Unknown subcommand \"{{sub}}\" for !{{cmd}}. Valid: {{valid}}."
105
+ },
106
+ "alerts": {
107
+ "noFallbackTitle": "manybot: no fallback driver",
108
+ "bothDriversFailedTitle": "manybot: sending failed on both drivers",
109
+ "updateAvailableTitle": "New manybot version available",
110
+ "updateAvailableMessage": "Installed: {{installed}} → available: {{available}}. See {{url}} for update instructions.",
111
+ "reconnectHaltedTitle": "manybot stopped reconnecting",
112
+ "reconnectHaltedMessage": "Gave up after {{attempts}} attempts — possible account restriction. Run connect() manually to retry."
113
+ },
114
+ "driver": {
115
+ "groupParticipantNotFound": "\"{{id}}\" does not match a current participant in group \"{{group}}\" (check whether the person is still in the group, or use a reply/mention instead of typing the number).",
116
+ "cannotKickSelf": "Refusing to remove the bot itself from the group via admin.kick().",
117
+ "groupParticipantsUpdateRejected": "groupParticipantsUpdate(\"{{action}}\") rejected for: {{detail}}",
118
+ "groupParticipantsUpdateFailed": "groupParticipantsUpdate(\"{{action}}\") failed for group \"{{group}}\" with participants [{{users}}]: {{message}}",
119
+ "pollDecryptFailed": "Failed to decrypt poll vote: {{error}}"
120
+ },
46
121
  "onboarding": {
47
122
  "intro": "ManyBot — first login",
48
123
  "methodPrompt": "How do you want to connect your WhatsApp account?",
@@ -69,17 +144,6 @@
69
144
  "retrying": "Retrying ({{attempt}}/{{max}})...",
70
145
  "sessionWiped": "Session discarded, pairing again ({{round}}/{{max}})...",
71
146
  "connectGaveUp": "Couldn't connect after several attempts. Check your network and try again."
72
- },
73
- "whatsmeow": {
74
- "installPrompt": "Install whatsmeow driver for fallback support? (EXPERIMENTAL — only text send & history work; other methods throw)",
75
- "unsupportedArch": "whatsmeow driver not available for {{os}}-{{arch}}. The bot will use Baileys only.",
76
- "fetchingTag": "Fetching latest whatsmeow release...",
77
- "fetchFailed": "Could not reach Codeberg. Skipping whatsmeow install.",
78
- "downloading": "Downloading {{url}}...",
79
- "downloadFailed": "Download failed: {{reason}}",
80
- "installTitle": "whatsmeow driver",
81
- "installed": "whatsmeow driver installed at {{path}}",
82
- "restartNotice": "Restart the bot for the whatsmeow driver to take effect.",
83
- "experimentalNotice": "whatsmeow is EXPERIMENTAL: only sendText and getHistory are implemented. sendImage, sendPoll, groupMetadata, and other methods throw."
84
147
  }
85
148
  }
149
+
@@ -30,19 +30,94 @@
30
30
  "schedulerError": "Error en la programación del plugin \"{{name}}\": {{message}}",
31
31
  "schedulerRegistered": "Programación registrada — plugin \"{{name}}\" → \"{{expression}}\"",
32
32
  "downloadJobFailed": "Error en el trabajo de descarga — {{message}}",
33
+ "downloadJobNoErrorFn": "El trabajo de descarga falló y no se pasó errorFn a enqueue()",
33
34
  "reconnecting": "Reconectando en {{secs}}s...",
34
35
  "reconnectHalted": "Se dejó de reconectar tras {{attempts}} intentos — llama a connect() manualmente para reintentar. Los bucles de reconexión repetidos pueden prolongar una restricción de WhatsApp.",
35
36
  "sessionExpired": "Sesión expirada o cerrada — eliminando sesión local y reiniciando...",
36
37
  "cacheLoaded": "{{count}} chat(s) cargado(s) desde la caché.",
37
38
  "cacheLoadedStale": "{{count}} chat(s) cargado(s) desde la caché (desactualizada).",
38
39
  "sendFailedNoFallback": "manybot: no hay driver de respaldo disponible (jid={{jid}}, primario={{driver}})",
39
- "sendFailedBothDrivers": "manybot: envío falló en ambos drivers (jid={{jid}}, se probó {{primary}} y luego {{secondary}})"
40
+ "sendFailedBothDrivers": "manybot: envío falló en ambos drivers (jid={{jid}}, se probó {{primary}} y luego {{secondary}})",
41
+ "commandsConfigMissingCmd": "commands.yaml: el comando \"{{id}}\" no tiene el campo obligatorio \"cmd\" — se omitió",
42
+ "commandRegistryInvalidPluginCommand": "commandRegistry: el plugin \"{{plugin}}\" comando \"{{function}}\" ({{id}}) tiene \"cmd\" inválido o ausente — se omitió",
43
+ "commandsConfigArgumentMissingName": "commands.yaml: la entrada de argumento bajo \"{{id}}\" no tiene el campo obligatorio \"name\" — se omitió",
44
+ "commandsConfigArgumentMissingType": "commands.yaml: el argumento \"{{name}}\" bajo \"{{id}}\" no tiene el campo obligatorio \"type\" — se omitió",
45
+ "commandsConfigUnknownArgType": "commands.yaml: el argumento \"{{name}}\" bajo \"{{id}}\" tiene un tipo desconocido \"{{type}}\" — se omitió",
46
+ "commandsConfigChoiceArgumentNoChoices": "commands.yaml: el argumento \"{{name}}\" bajo \"{{id}}\" es de tipo \"choice\" pero no declaró ninguna opción — usando placeholder genérico en la sugerencia de uso",
47
+ "commandsConfigArgumentsNotList": "commands.yaml: arguments bajo \"{{id}}\" debe ser una lista — se ignoró",
48
+ "commandsConfigSubcommandMissingCmd": "commands.yaml: el subcomando bajo \"{{id}}\" no tiene el campo obligatorio \"cmd\" — se omitió",
49
+ "commandsConfigSubcommandsNotList": "commands.yaml: subcommands bajo \"{{id}}\" debe ser una lista — se ignoró",
50
+ "commandsConfigDuplicateSubcommand": "commands.yaml: subcomando duplicado \"{{cmd}}\" bajo \"{{id}}\" — se conserva el primero",
51
+ "commandsConfigReadFailed": "commands.yaml: no se pudo leer ({{path}}): {{message}}",
52
+ "commandsConfigParseFailed": "commands.yaml: error de parseo YAML: {{message}}",
53
+ "commandsConfigInvalidRoot": "commands.yaml: el nivel raíz debe ser un mapa de entradas de comandos",
54
+ "commandsConfigInvalidEntry": "commands.yaml: la entrada \"{{id}}\" no es un objeto — se omitió",
55
+ "commandsConfigImportReadFailed": "commands.yaml: no se pudo leer el import \"{{path}}\": {{message}}",
56
+ "commandsConfigImportParseFailed": "commands.yaml: error de parseo YAML en el import \"{{path}}\": {{message}}",
57
+ "commandsConfigImportInvalidRoot": "commands.yaml: el import \"{{path}}\" debe tener un mapa de secciones en el nivel raíz — se omitió",
58
+ "commandsConfigImportNested": "commands.yaml: el import \"{{path}}\" declara su propio \"import\" — los imports anidados no son compatibles y se ignoraron",
59
+ "commandsConfigImportKeyConflict": "commands.yaml: el import \"{{path}}\" redefine \"{{key}}\", que ya pertenece a \"{{owner}}\" — se conserva el primero",
60
+ "commandsConfigCommandsWrapperInvalid": "commands.yaml: el bloque superior \"commands:\" debe ser un mapa de comandos — se omitió",
61
+ "commandsConfigCommandsWrapperCollision": "commands.yaml: el bloque \"commands:\" define \"{{key}}\", que ya existe en el nivel superior — se conserva la entrada superior",
62
+ "commandsConfigConflictingScopeFlags": "commands.yaml: \"permissions\" tiene group_only y dm_only a la vez — elegí uno (o usá \"scope:\" directo)",
63
+ "commandsConfigLoadingPresetMissing": "commands.yaml: \"{{id}}\" referencia el preset de loading \"{{name}}\" que no existe — el campo queda vacío",
64
+ "commandsConfigLoadingInvalid": "commands.yaml: \"{{id}}\" el valor de loading no es nombre de preset ni objeto inline — se omitió",
65
+ "commandsConfigLoadingUnknownType": "commands.yaml: \"{{id}}\" el tipo de loading \"{{type}}\" no es uno de reaction|typing|recording_audio|spinner|none — se omitió",
66
+ "commandsConfigLoadingUnknownProp": "commands.yaml: \"{{id}}\" el tipo de loading \"{{type}}\" no acepta la propiedad \"{{key}}\" — la entrada está malformada y se descartó",
67
+ "commandRegistryOrphanEntry": "commandRegistry: la entrada \"{{id}}\" apunta a {{plugin}}.{{function}} pero ningún plugin activo la provee — se omitió",
68
+ "commandRegistryInvalidEntry": "commandRegistry: la entrada \"{{id}}\" no tiene plugin/function ni text — se omitió",
69
+ "commandRegistryInvocationCollision": "commandRegistry: la invocación \"{{text}}\" ya pertenece a \"{{winner}}\" — descartando {{loser}} ({{via}})",
70
+ "commandDeprecationRenamed": "commandRegistry: comando \"{{id}}\" renombrado de \"{{old}}\" a \"{{new}}\" — notificando a quienes llamen al antiguo durante {{days}} días",
71
+ "commandDeprecationRemoved": "commandRegistry: comando \"{{id}}\" (cmd \"{{old}}\") fue eliminado — notificando a quienes llamen al antiguo durante {{days}} días",
72
+ "commandDeprecationFallbackRenamed": "El comando \"{{old}}\" se renombró a \"{{new}}\". Actualiza tu mensaje.",
73
+ "commandDeprecationFallbackRemoved": "El comando \"{{old}}\" ya no está disponible.",
74
+ "commandDeprecationReservedInvocation": "commandRegistry: la invocación \"{{text}}\" está reservada por deprecation activa de \"{{id}}\" — registro rechazado",
75
+ "commandManualMissing": "No hay manual detallado disponible para el comando \"{{cmd}}\".",
76
+ "commandNotFound": "Comando o categoría \"{{cmd}}\" no encontrado. Usa {{prefix}}{{menuCmd}} para ver la lista de comandos.",
77
+ "commandNotFoundSuggestion": "Comando \"{{cmd}}\" no encontrado. ¿Quisiste decir {{prefix}}{{suggestion}}? Usa {{prefix}}{{menuCmd}} para ver la lista de comandos.",
78
+ "commandRegistryMenuAliasCollision": "commandRegistry: alias de menú \"{{alias}}\" colisionó con comando real \"{{winner}}\" — alias de menú ignorado"
40
79
  },
41
80
  "errors": {
42
81
  "stack": "Stack",
43
82
  "invalid_config": "Archivo de configuración inválido {{file}}: {{error}}",
44
83
  "fix_config": "Corrige la sintaxis en {{file}} antes de iniciar el bot."
45
84
  },
85
+ "menu": {
86
+ "intro": "Usa {prefix}<comando> para ejecutarlo o {prefix}help <comando> para ver el manual.",
87
+ "other": "Otros",
88
+ "category": "Categoría",
89
+ "manual": "Manual",
90
+ "description": "Descripción"
91
+ },
92
+ "commandPermissions": {
93
+ "botNotAdmin": "Este comando requiere que el bot sea administrador del grupo.",
94
+ "senderNotAdmin": "Este comando es solo para administradores del grupo.",
95
+ "ownerOnly": "Solo el dueño del bot puede usar este comando.",
96
+ "donoOnly": "Solo el dueño designado de este comando puede usarlo.",
97
+ "wrongScope": "Este comando no se puede usar en este contexto.",
98
+ "cooldown": "Esperá {{seconds}}s antes de usar este comando de nuevo.",
99
+ "blacklist": "No podés usar este comando.",
100
+ "allowedChats": "Este comando no está disponible en este chat."
101
+ },
102
+ "commandRun": {
103
+ "missingRequiredArg": "Faltan argumentos obligatorios.",
104
+ "unknownSubcommand": "Subcomando \"{{sub}}\" desconocido para !{{cmd}}. Válidos: {{valid}}."
105
+ },
106
+ "alerts": {
107
+ "noFallbackTitle": "manybot: sin driver de fallback",
108
+ "bothDriversFailedTitle": "manybot: envío fallido en ambos drivers",
109
+ "updateAvailableTitle": "Nueva versión del manybot disponible",
110
+ "updateAvailableMessage": "Instalado: {{instalado}} → disponible: {{disponible}}. Vea {{url}} para instrucciones de actualización.",
111
+ "reconnectHaltedTitle": "manybot detuvo la reconexión",
112
+ "reconnectHaltedMessage": "Desistió después de {{intento}} intentos — posible restricción de la cuenta. Ejecute connect() manualmente para intentar nuevamente."
113
+ },
114
+ "driver": {
115
+ "groupParticipantNotFound": "\"{{id}}\" no coincide con ningún participante actual del grupo \"{{group}}\" (comprueba si la persona sigue en el grupo o usa una respuesta/mención en lugar de escribir el número).",
116
+ "cannotKickSelf": "Se rechaza eliminar al propio bot del grupo mediante admin.kick().",
117
+ "groupParticipantsUpdateRejected": "groupParticipantsUpdate(\"{{action}}\") rechazado para: {{detail}}",
118
+ "groupParticipantsUpdateFailed": "groupParticipantsUpdate(\"{{action}}\") falló para el grupo \"{{group}}\" con participantes [{{users}}]: {{message}}",
119
+ "pollDecryptFailed": "No se pudo descifrar el voto de la encuesta: {{error}}"
120
+ },
46
121
  "onboarding": {
47
122
  "intro": "ManyBot — primer inicio de sesión",
48
123
  "methodPrompt": "¿Cómo quieres conectar tu cuenta de WhatsApp?",
@@ -69,17 +144,6 @@
69
144
  "retrying": "Reintentando ({{attempt}}/{{max}})...",
70
145
  "sessionWiped": "Sesión descartada, emparejando de nuevo ({{round}}/{{max}})...",
71
146
  "connectGaveUp": "No se pudo conectar tras varios intentos. Revisa tu conexión e intenta de nuevo."
72
- },
73
- "whatsmeow": {
74
- "installPrompt": "¿Instalar el driver whatsmeow para soporte de fallback? (EXPERIMENTAL — solo sendText e historial funcionan; otros métodos lanzan error)",
75
- "unsupportedArch": "Driver whatsmeow no disponible para {{os}}-{{arch}}. El bot usará solo Baileys.",
76
- "fetchingTag": "Obteniendo última versión de whatsmeow...",
77
- "fetchFailed": "No se pudo contactar a Codeberg. Instalación de whatsmeow omitida.",
78
- "downloading": "Descargando {{url}}...",
79
- "downloadFailed": "Descarga fallida: {{reason}}",
80
- "installTitle": "Driver whatsmeow",
81
- "installed": "Driver whatsmeow instalado en {{path}}",
82
- "restartNotice": "Reinicia el bot para que el driver whatsmeow surta efecto.",
83
- "experimentalNotice": "whatsmeow es EXPERIMENTAL: solo sendText y getHistory están implementados. sendImage, sendPoll, groupMetadata y otros métodos lanzan error."
84
147
  }
85
148
  }
149
+
@@ -30,19 +30,94 @@
30
30
  "schedulerError": "Erro no agendamento do plugin \"{{name}}\": {{message}}",
31
31
  "schedulerRegistered": "Agendamento registrado — plugin \"{{name}}\" → \"{{expression}}\"",
32
32
  "downloadJobFailed": "Falha no job de download — {{message}}",
33
+ "downloadJobNoErrorFn": "Job de download falhou e nenhum errorFn foi passado para enqueue()",
33
34
  "reconnecting": "Reconectando em {{secs}}s...",
34
35
  "reconnectHalted": "Desisti de reconectar após {{attempts}} tentativas — chame connect() manualmente para tentar de novo. Loops de reconexão repetidos podem prolongar uma restrição do WhatsApp.",
35
36
  "sessionExpired": "Sessão expirada ou desconectada — removendo sessão local e reiniciando...",
36
37
  "cacheLoaded": "{{count}} conversa(s) carregada(s) do cache.",
37
38
  "cacheLoadedStale": "{{count}} conversa(s) carregada(s) do cache (desatualizado).",
38
39
  "sendFailedNoFallback": "manybot: nenhum driver de fallback disponível (jid={{jid}}, primário={{driver}})",
39
- "sendFailedBothDrivers": "manybot: envio falhou nos dois drivers (jid={{jid}}, tentou {{primary}} depois {{secondary}})"
40
+ "sendFailedBothDrivers": "manybot: envio falhou nos dois drivers (jid={{jid}}, tentou {{primary}} depois {{secondary}})",
41
+ "commandsConfigMissingCmd": "commands.yaml: comando \"{{id}}\" sem o campo obrigatório \"cmd\" — ignorado",
42
+ "commandRegistryInvalidPluginCommand": "commandRegistry: plugin \"{{plugin}}\" comando \"{{function}}\" ({{id}}) tem \"cmd\" inválido ou ausente — ignorado",
43
+ "commandsConfigArgumentMissingName": "commands.yaml: entrada de argumento sob \"{{id}}\" sem o campo obrigatório \"name\" — ignorada",
44
+ "commandsConfigArgumentMissingType": "commands.yaml: argumento \"{{name}}\" sob \"{{id}}\" sem o campo obrigatório \"type\" — ignorado",
45
+ "commandsConfigUnknownArgType": "commands.yaml: argumento \"{{name}}\" sob \"{{id}}\" com tipo desconhecido \"{{type}}\" — ignorado",
46
+ "commandsConfigChoiceArgumentNoChoices": "commands.yaml: argumento \"{{name}}\" sob \"{{id}}\" é do tipo \"choice\" mas não declarou nenhuma opção — usando placeholder genérico na sugestão de uso",
47
+ "commandsConfigArgumentsNotList": "commands.yaml: arguments sob \"{{id}}\" precisa ser uma lista — ignorado",
48
+ "commandsConfigSubcommandMissingCmd": "commands.yaml: subcomando sob \"{{id}}\" sem o campo obrigatório \"cmd\" — ignorado",
49
+ "commandsConfigSubcommandsNotList": "commands.yaml: subcommands sob \"{{id}}\" precisa ser uma lista — ignorado",
50
+ "commandsConfigDuplicateSubcommand": "commands.yaml: subcomando duplicado \"{{cmd}}\" sob \"{{id}}\" — mantendo o primeiro",
51
+ "commandsConfigReadFailed": "commands.yaml: não foi possível ler ({{path}}): {{message}}",
52
+ "commandsConfigParseFailed": "commands.yaml: erro de parse YAML: {{message}}",
53
+ "commandsConfigInvalidRoot": "commands.yaml: o nível raiz deve ser um mapa de entradas de comando",
54
+ "commandsConfigInvalidEntry": "commands.yaml: entrada \"{{id}}\" não é um objeto — ignorada",
55
+ "commandsConfigImportReadFailed": "commands.yaml: não foi possível ler o import \"{{path}}\": {{message}}",
56
+ "commandsConfigImportParseFailed": "commands.yaml: erro de parse YAML no import \"{{path}}\": {{message}}",
57
+ "commandsConfigImportInvalidRoot": "commands.yaml: o import \"{{path}}\" precisa ter um mapa de seções no nível raiz — ignorado",
58
+ "commandsConfigImportNested": "commands.yaml: o import \"{{path}}\" declara seu próprio \"import\" — imports aninhados não são suportados e foram ignorados",
59
+ "commandsConfigImportKeyConflict": "commands.yaml: o import \"{{path}}\" redefine \"{{key}}\", que já pertence a \"{{owner}}\" — mantendo o primeiro",
60
+ "commandsConfigCommandsWrapperInvalid": "commands.yaml: o bloco \"commands:\" de topo precisa ser um mapa de comandos — ignorado",
61
+ "commandsConfigCommandsWrapperCollision": "commands.yaml: o bloco \"commands:\" define \"{{key}}\", que já existe no nível de topo — mantendo a entrada do topo",
62
+ "commandsConfigConflictingScopeFlags": "commands.yaml: \"permissions\" tem group_only e dm_only ao mesmo tempo — escolha um (ou use \"scope:\" direto)",
63
+ "commandsConfigLoadingPresetMissing": "commands.yaml: \"{{id}}\" referencia o preset de loading \"{{name}}\" que não existe — campo fica vazio",
64
+ "commandsConfigLoadingInvalid": "commands.yaml: \"{{id}}\" o valor de loading não é nome de preset nem objeto inline — ignorado",
65
+ "commandsConfigLoadingUnknownType": "commands.yaml: \"{{id}}\" o tipo de loading \"{{type}}\" não é um dos reaction|typing|recording_audio|spinner|none — ignorado",
66
+ "commandsConfigLoadingUnknownProp": "commands.yaml: \"{{id}}\" o tipo de loading \"{{type}}\" não aceita a propriedade \"{{key}}\" — entrada malformada, descartada",
67
+ "commandRegistryOrphanEntry": "commandRegistry: entrada \"{{id}}\" aponta para {{plugin}}.{{function}} mas nenhum plugin ativo fornece — ignorada",
68
+ "commandRegistryInvalidEntry": "commandRegistry: entrada \"{{id}}\" não tem plugin/function nem text — ignorada",
69
+ "commandRegistryInvocationCollision": "commandRegistry: invocação \"{{text}}\" já pertence a \"{{winner}}\" — descartando {{loser}} ({{via}})",
70
+ "commandDeprecationRenamed": "commandRegistry: comando \"{{id}}\" renomeado de \"{{old}}\" para \"{{new}}\" — avisando chamadores antigos por {{days}} dias",
71
+ "commandDeprecationRemoved": "commandRegistry: comando \"{{id}}\" (cmd \"{{old}}\") foi removido — avisando chamadores antigos por {{days}} dias",
72
+ "commandDeprecationFallbackRenamed": "O comando \"{{old}}\" foi renomeado para \"{{new}}\". Atualize sua mensagem.",
73
+ "commandDeprecationFallbackRemoved": "O comando \"{{old}}\" não está mais disponível.",
74
+ "commandDeprecationReservedInvocation": "commandRegistry: invocação \"{{text}}\" está reservada por deprecation ativa de \"{{id}}\" — registro recusado",
75
+ "commandManualMissing": "Nenhum manual detalhado disponível para o comando \"{{cmd}}\".",
76
+ "commandNotFound": "Comando ou categoria \"{{cmd}}\" não encontrado. Use {{prefix}}{{menuCmd}} para ver a lista de comandos.",
77
+ "commandNotFoundSuggestion": "Comando \"{{cmd}}\" não encontrado. Você quis dizer {{prefix}}{{suggestion}}? Use {{prefix}}{{menuCmd}} para ver a lista de comandos.",
78
+ "commandRegistryMenuAliasCollision": "commandRegistry: alias de menu \"{{alias}}\" colidiu com comando real \"{{winner}}\" — alias de menu ignorado"
40
79
  },
41
80
  "errors": {
42
81
  "stack": "Stack",
43
82
  "invalid_config": "Arquivo de configuração inválido {{file}}: {{error}}",
44
83
  "fix_config": "Corrija a sintaxe em {{file}} antes de iniciar o bot."
45
84
  },
85
+ "menu": {
86
+ "intro": "Use {prefix}<comando> para executar ou {prefix}help <comando> para ver o manual.",
87
+ "other": "Outros",
88
+ "category": "Categoria",
89
+ "manual": "Manual",
90
+ "description": "Descrição"
91
+ },
92
+ "commandPermissions": {
93
+ "botNotAdmin": "Esse comando precisa que o bot seja admin do grupo.",
94
+ "senderNotAdmin": "Esse comando é só pra admins do grupo.",
95
+ "ownerOnly": "Só o dono do bot pode usar esse comando.",
96
+ "donoOnly": "Só o dono designado desse comando pode usá-lo.",
97
+ "wrongScope": "Esse comando não funciona aqui.",
98
+ "cooldown": "Espera {{seconds}}s antes de usar esse comando de novo.",
99
+ "blacklist": "Você não pode usar esse comando.",
100
+ "allowedChats": "Esse comando não está disponível nesse chat."
101
+ },
102
+ "commandRun": {
103
+ "missingRequiredArg": "Faltam argumentos obrigatórios.",
104
+ "unknownSubcommand": "Subcomando \"{{sub}}\" desconhecido para !{{cmd}}. Válidos: {{valid}}."
105
+ },
106
+ "alerts": {
107
+ "noFallbackTitle": "manybot: sem driver de fallback",
108
+ "bothDriversFailedTitle": "manybot: envio falhou nos dois drivers",
109
+ "updateAvailableTitle": "Nova versão do manybot disponível",
110
+ "updateAvailableMessage": "Instalado: {{installed}} → disponível: {{available}}. Veja {{url}} para instruções de atualização.",
111
+ "reconnectHaltedTitle": "manybot parou de reconectar",
112
+ "reconnectHaltedMessage": "Desistiu após {{attempts}} tentativas — possível restrição da conta. Execute connect() manualmente para tentar novamente."
113
+ },
114
+ "driver": {
115
+ "groupParticipantNotFound": "\"{{id}}\" não corresponde a nenhum participante atual do grupo \"{{group}}\" (confira se a pessoa ainda está no grupo, ou use reply/menção em vez de digitar o número).",
116
+ "cannotKickSelf": "Removendo o próprio bot do grupo via admin.kick() recusado.",
117
+ "groupParticipantsUpdateRejected": "groupParticipantsUpdate(\"{{action}}\") rejeitado para: {{detail}}",
118
+ "groupParticipantsUpdateFailed": "groupParticipantsUpdate(\"{{action}}\") falhou para o grupo \"{{group}}\" com participantes [{{users}}]: {{message}}",
119
+ "pollDecryptFailed": "Erro ao decriptar voto da enquete: {{error}}"
120
+ },
46
121
  "onboarding": {
47
122
  "intro": "ManyBot — primeiro login",
48
123
  "methodPrompt": "Como você quer conectar sua conta do WhatsApp?",
@@ -69,17 +144,6 @@
69
144
  "retrying": "Tentando novamente ({{attempt}}/{{max}})...",
70
145
  "sessionWiped": "Sessão descartada, tentando parear novamente ({{round}}/{{max}})...",
71
146
  "connectGaveUp": "Não foi possível conectar após várias tentativas. Verifique sua conexão e tente de novo."
72
- },
73
- "whatsmeow": {
74
- "installPrompt": "Instalar driver whatsmeow para suporte a fallback? (EXPERIMENTAL — apenas sendText e histórico funcionam; outros métodos lançam erro)",
75
- "unsupportedArch": "Driver whatsmeow não disponível para {{os}}-{{arch}}. O bot usará apenas Baileys.",
76
- "fetchingTag": "Buscando última versão do whatsmeow...",
77
- "fetchFailed": "Não foi possível acessar o Codeberg. Instalação do whatsmeow ignorada.",
78
- "downloading": "Baixando {{url}}...",
79
- "downloadFailed": "Download falhou: {{reason}}",
80
- "installTitle": "Driver whatsmeow",
81
- "installed": "Driver whatsmeow instalado em {{path}}",
82
- "restartNotice": "Reinicie o bot para que o driver whatsmeow entre em efeito.",
83
- "experimentalNotice": "whatsmeow é EXPERIMENTAL: apenas sendText e getHistory estão implementados. sendImage, sendPoll, groupMetadata e outros métodos lançam erro."
84
147
  }
85
148
  }
149
+