synabun 2.0.5 → 2.0.7
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/CHANGELOG.md +40 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +13 -0
- package/docs/whatsapp.md +151 -16
- package/hooks/claude-code/shared.mjs +6 -3
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/packaged-runtime.js +138 -11
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -7
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +34 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tui.js +2 -1
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +33 -14
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tui.ts +2 -1
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +627 -57
- package/neural-interface/i18n/pt-BR.json +626 -56
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/assistant-api.js +255 -52
- package/neural-interface/lib/assistant-brains/claude.js +15 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-config.js +62 -14
- package/neural-interface/lib/assistant-dispatch.js +432 -125
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-persona.js +68 -11
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +307 -47
- package/neural-interface/lib/assistant-user-writer.js +4 -2
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +90 -3
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +3 -3
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/codex-account-auth.js +178 -0
- package/neural-interface/lib/codex-accounts.js +489 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +120 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-read.js +34 -2
- package/neural-interface/lib/install-details.js +145 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/native-loop-providers.js +16 -3
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/session-title-generator.js +4 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +2 -2
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/offline.html +16 -1
- package/neural-interface/public/onboarding.html +96 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +539 -0
- package/neural-interface/public/shared/account-usage.js +19 -0
- package/neural-interface/public/shared/account-view.js +335 -0
- package/neural-interface/public/shared/agent-runtime-options.js +28 -8
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +32 -2
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +124 -25
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
- package/neural-interface/public/shared/assistant/asst-menu.js +15 -3
- package/neural-interface/public/shared/assistant/asst-panel.js +48 -14
- package/neural-interface/public/shared/assistant/asst-render.js +38 -7
- package/neural-interface/public/shared/assistant/asst-route.js +72 -14
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +1 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +99 -17
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +48 -107
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +128 -107
- package/neural-interface/public/shared/cdx/cdx-tabs.js +261 -664
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +46 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-panel.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +1 -1
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/html-shell.js +1 -0
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +32 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +118 -0
- package/neural-interface/public/shared/pwa-install-page.js +3 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-ia.js +15 -0
- package/neural-interface/public/shared/showcase-privacy.js +489 -0
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +278 -31
- package/neural-interface/public/shared/synabun-mascot.js +48 -2
- package/neural-interface/public/shared/ui-claude-panel.js +212 -213
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-loading.js +30 -6
- package/neural-interface/public/shared/ui-menubar.js +13 -3
- package/neural-interface/public/shared/ui-navbar.js +3 -1
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +55 -3
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +6 -0
- package/neural-interface/public/shared/ui-terminal.js +17 -0
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-update.js +594 -5
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +2 -0
- package/neural-interface/public/sw.js +1 -1
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/server.js +1219 -608
- package/package.json +4 -1
- package/setup.js +9 -8
|
@@ -10,12 +10,17 @@
|
|
|
10
10
|
// Two kinds of fields:
|
|
11
11
|
// user what Settings edits (update(), validated, versioned: a stale
|
|
12
12
|
// expectedVersion is a 409 like lib/assistant-config.js)
|
|
13
|
-
// service what SynaBun owns (mode, the conversation, the bound owner,
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// service what SynaBun owns (mode, the conversation, the bound owner,
|
|
14
|
+
// pause) — written by the service and the bridge through
|
|
15
|
+
// write(), refused in update().
|
|
16
16
|
// Raising the level to 'autonomous' never applies here: update() needs
|
|
17
17
|
// confirmEscalation and answers {pending:true}; the service applies it after
|
|
18
|
-
// the phone answers "ALLOW <code>". Lowering applies at once.
|
|
18
|
+
// the phone answers "ALLOW <code>". Lowering applies at once. Autonomous then
|
|
19
|
+
// stays on until the owner lowers it in Settings, restarts included: there is
|
|
20
|
+
// no time limit. `autonomousUntil` is a legacy field (the end of the 8-hour
|
|
21
|
+
// window of 2.0.5 and before): a level 'autonomous' stored with one was turned
|
|
22
|
+
// on under that rule and is read as 'ask' (migrateLegacyAutonomous), so an
|
|
23
|
+
// update never puts anyone back into Autonomous.
|
|
19
24
|
// `computerUse` is the owner's switch for controlling this Mac from a
|
|
20
25
|
// WhatsApp conversation: off by default, a plain boolean, changed only through
|
|
21
26
|
// update() (Settings on this computer: the guarded PUT /api/whatsapp/config).
|
|
@@ -25,9 +30,19 @@
|
|
|
25
30
|
// Assistant) or { provider, model, effort }. Its shape is checked here; the
|
|
26
31
|
// service checks the model and the effort against the Assistant's catalog
|
|
27
32
|
// before it calls update() (lib/whatsapp/brain.js).
|
|
33
|
+
// Voice notes (lib/whatsapp/transcription.js): `transcriptionEnabled`, the
|
|
34
|
+
// spoken `transcriptionLanguage` ('auto' or a 2-3 letter code) and three
|
|
35
|
+
// paths, '' meaning "find it by itself": the whisper-cli program, the ggml
|
|
36
|
+
// model and FFmpeg. A path is the absolute path of an existing regular file
|
|
37
|
+
// (a program executable), checked when update() stores it and again by the
|
|
38
|
+
// host right before it runs; it is never looked up on PATH. Only update()
|
|
39
|
+
// (Settings on this computer) sets a path: a service write may only clear one,
|
|
40
|
+
// so nothing but the person at this computer chooses what the host runs.
|
|
28
41
|
|
|
29
42
|
import { LEVELS } from '../remote-policy.js';
|
|
30
43
|
import { cleanBrainChoice } from './brain.js';
|
|
44
|
+
import { TRANSCRIPTION_LANGUAGE_RE } from './protocol.js';
|
|
45
|
+
import { checkToolPath } from './transcription.js';
|
|
31
46
|
|
|
32
47
|
export const WHATSAPP_CONFIG_KEY = 'whatsapp_config';
|
|
33
48
|
|
|
@@ -44,6 +59,11 @@ export const USER_DEFAULTS = Object.freeze({
|
|
|
44
59
|
strictWorkerApprovals: false,
|
|
45
60
|
computerUse: false,
|
|
46
61
|
brain: null,
|
|
62
|
+
transcriptionEnabled: true,
|
|
63
|
+
transcriptionLanguage: 'auto',
|
|
64
|
+
transcriptionEnginePath: '',
|
|
65
|
+
transcriptionModelPath: '',
|
|
66
|
+
transcriptionFfmpegPath: '',
|
|
47
67
|
});
|
|
48
68
|
|
|
49
69
|
export const SERVICE_DEFAULTS = Object.freeze({
|
|
@@ -51,7 +71,7 @@ export const SERVICE_DEFAULTS = Object.freeze({
|
|
|
51
71
|
sessionId: null,
|
|
52
72
|
previousSessionIds: Object.freeze([]),
|
|
53
73
|
owner: null, // { masked, boundAt, via } — never a number
|
|
54
|
-
autonomousUntil: null, //
|
|
74
|
+
autonomousUntil: null, // legacy: the end of 2.0.5's 8-hour Autonomous window; always null now (migrateLegacyAutonomous)
|
|
55
75
|
paused: false,
|
|
56
76
|
pausedBy: null, // 'desktop' | 'phone'
|
|
57
77
|
version: 0,
|
|
@@ -68,6 +88,11 @@ export const ROTATIONS = Object.freeze(['idle6h', 'daily', 'never']);
|
|
|
68
88
|
export const SELF_TRIGGERS = Object.freeze(['all', 'prefix']);
|
|
69
89
|
export const MODES = Object.freeze(['self', 'dedicated']);
|
|
70
90
|
export const REPLY_LABEL_MAX = 24;
|
|
91
|
+
/** The voice-note settings the host gets (service prefsOf → protocol.js PREFS_SPEC). */
|
|
92
|
+
export const TRANSCRIPTION_FIELDS = Object.freeze(['transcriptionEnabled', 'transcriptionLanguage', 'transcriptionEnginePath', 'transcriptionModelPath', 'transcriptionFfmpegPath']);
|
|
93
|
+
/** The path fields and what each must name (transcription.js checkToolPath). */
|
|
94
|
+
export const TOOL_PATH_FIELDS = Object.freeze({ transcriptionEnginePath: 'program', transcriptionModelPath: 'file', transcriptionFfmpegPath: 'program' });
|
|
95
|
+
const TOOL_PATH_MAX = 4096;
|
|
71
96
|
|
|
72
97
|
const RANK = Object.freeze({ 'read-only': 0, ask: 1, autonomous: 2 });
|
|
73
98
|
|
|
@@ -81,6 +106,28 @@ function configError(field, message, { code = 'CONFIG_INVALID', status = 400 } =
|
|
|
81
106
|
return error;
|
|
82
107
|
}
|
|
83
108
|
|
|
109
|
+
/** A tool path's shape: '' (find it by itself) or an absolute path of one line; undefined when it has neither shape. */
|
|
110
|
+
function cleanToolPath(value) {
|
|
111
|
+
if (value === null || value === undefined) return '';
|
|
112
|
+
if (typeof value !== 'string') return undefined;
|
|
113
|
+
const text = value.trim();
|
|
114
|
+
if (!text) return '';
|
|
115
|
+
if (text.length > TOOL_PATH_MAX || /[\u0000-\u001f\u007f]/.test(text)) return undefined;
|
|
116
|
+
return checkToolPath(text, 'program', { fs: SHAPE_ONLY_FS }) === 'not_absolute' ? undefined : text;
|
|
117
|
+
}
|
|
118
|
+
// checkToolPath against nothing on disk: only its shape rules run (absolute, not a network path).
|
|
119
|
+
const SHAPE_ONLY_FS = Object.freeze({ statSync: () => { throw new Error('shape only'); }, accessSync: () => {}, constants: {} });
|
|
120
|
+
|
|
121
|
+
const TOOL_PATH_PROBLEMS = Object.freeze({
|
|
122
|
+
not_absolute: 'must be an absolute path (it is never looked up on PATH)',
|
|
123
|
+
network: 'must be a path on this computer, not on a network share',
|
|
124
|
+
missing: 'names a file that does not exist',
|
|
125
|
+
not_file: 'must name a regular file',
|
|
126
|
+
not_executable: process.platform === 'win32' ? 'must name a program (.exe)' : 'names a file that is not executable (chmod +x it)',
|
|
127
|
+
unreadable: 'names a file this account cannot read',
|
|
128
|
+
empty: 'names an empty file',
|
|
129
|
+
});
|
|
130
|
+
|
|
84
131
|
/** A reply label: printable, one line, 1-24 characters; '' → null (the mode's default label). */
|
|
85
132
|
function cleanLabel(value) {
|
|
86
133
|
if (value === null || value === undefined) return null;
|
|
@@ -102,6 +149,8 @@ const CHECKS = {
|
|
|
102
149
|
activityText: (v) => typeof v === 'boolean',
|
|
103
150
|
strictWorkerApprovals: (v) => typeof v === 'boolean',
|
|
104
151
|
computerUse: (v) => typeof v === 'boolean',
|
|
152
|
+
transcriptionEnabled: (v) => typeof v === 'boolean',
|
|
153
|
+
transcriptionLanguage: (v) => typeof v === 'string' && TRANSCRIPTION_LANGUAGE_RE.test(v),
|
|
105
154
|
};
|
|
106
155
|
const MESSAGES = {
|
|
107
156
|
enabled: 'enabled must be true or false',
|
|
@@ -116,10 +165,21 @@ const MESSAGES = {
|
|
|
116
165
|
strictWorkerApprovals: 'strictWorkerApprovals must be true or false',
|
|
117
166
|
computerUse: 'computerUse must be true or false',
|
|
118
167
|
brain: 'brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex or opencode',
|
|
168
|
+
transcriptionEnabled: 'transcriptionEnabled must be true or false',
|
|
169
|
+
transcriptionLanguage: "transcriptionLanguage must be 'auto' or a 2-3 letter lowercase language code, such as en or pt",
|
|
170
|
+
transcriptionEnginePath: 'transcriptionEnginePath must be empty (find whisper-cli by itself) or the absolute path of the whisper-cli program',
|
|
171
|
+
transcriptionModelPath: 'transcriptionModelPath must be empty (ggml-small.bin in the transcription folder) or the absolute path of a whisper.cpp model file',
|
|
172
|
+
transcriptionFfmpegPath: 'transcriptionFfmpegPath must be empty (find ffmpeg by itself) or the absolute path of the ffmpeg program',
|
|
119
173
|
};
|
|
120
174
|
|
|
121
|
-
/**
|
|
122
|
-
|
|
175
|
+
/** Whether a tool path can be used on this computer: null, or why not (transcription.js checkToolPath). */
|
|
176
|
+
const defaultCheckPath = (value, kind) => checkToolPath(value, kind);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Validate a user patch (Settings fields only). → the cleaned patch; throws 400 CONFIG_INVALID {field}.
|
|
180
|
+
* `checkPath(value, kind)` → null or a problem: how a tool path is checked on disk (tests pass a fake).
|
|
181
|
+
*/
|
|
182
|
+
export function validateConfigPatch(patch = {}, { checkPath = defaultCheckPath } = {}) {
|
|
123
183
|
if (!isPlainObject(patch)) throw configError('config', 'config must be an object');
|
|
124
184
|
const out = {};
|
|
125
185
|
for (const [key, value] of Object.entries(patch)) {
|
|
@@ -138,6 +198,14 @@ export function validateConfigPatch(patch = {}) {
|
|
|
138
198
|
out.brain = brain;
|
|
139
199
|
continue;
|
|
140
200
|
}
|
|
201
|
+
if (TOOL_PATH_FIELDS[key]) {
|
|
202
|
+
const toolPath = cleanToolPath(value);
|
|
203
|
+
if (toolPath === undefined) throw configError(key, MESSAGES[key]);
|
|
204
|
+
const problem = toolPath ? checkPath(toolPath, TOOL_PATH_FIELDS[key]) : null;
|
|
205
|
+
if (problem) throw configError(key, `${key} ${TOOL_PATH_PROBLEMS[problem] || 'names a file that cannot be used'}: ${toolPath}`);
|
|
206
|
+
out[key] = toolPath;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
141
209
|
if (!CHECKS[key](value)) throw configError(key, MESSAGES[key]);
|
|
142
210
|
out[key] = value;
|
|
143
211
|
}
|
|
@@ -157,13 +225,28 @@ function cleanOwner(value) {
|
|
|
157
225
|
return { masked, boundAt: toMs(value.boundAt), via: value.via === 'claim' ? 'claim' : 'self_confirm' };
|
|
158
226
|
}
|
|
159
227
|
|
|
228
|
+
/**
|
|
229
|
+
* A level 'autonomous' stored with an `autonomousUntil` (any value but null) was turned on under
|
|
230
|
+
* the 8-hour rule of 2.0.5 and before, whether that window is still open or not: it does not
|
|
231
|
+
* become Autonomous without a time limit. → the stored value as level 'ask' with no
|
|
232
|
+
* autonomousUntil, or null when `stored` is not such a value. Autonomous stored with
|
|
233
|
+
* autonomousUntil null is today's state and stays.
|
|
234
|
+
*/
|
|
235
|
+
export function migrateLegacyAutonomous(stored) {
|
|
236
|
+
if (!isPlainObject(stored) || stored.level !== 'autonomous') return null;
|
|
237
|
+
if (stored.autonomousUntil === null || stored.autonomousUntil === undefined) return null;
|
|
238
|
+
return { ...stored, level: 'ask', autonomousUntil: null };
|
|
239
|
+
}
|
|
240
|
+
|
|
160
241
|
/** Defaults + stored values; a bad stored value falls back to its default, field by field. */
|
|
161
242
|
export function effectiveConfig(stored = {}) {
|
|
162
|
-
const raw = isPlainObject(stored) ? stored : {};
|
|
243
|
+
const raw = isPlainObject(stored) ? migrateLegacyAutonomous(stored) || stored : {};
|
|
163
244
|
const out = {};
|
|
164
245
|
for (const key of USER_FIELDS) {
|
|
165
246
|
if (key === 'replyLabel') { const label = cleanLabel(raw.replyLabel); out.replyLabel = label === undefined ? null : label; continue; }
|
|
166
247
|
if (key === 'brain') { const brain = cleanBrainChoice(raw.brain); out.brain = brain === undefined ? null : brain; continue; }
|
|
248
|
+
// A stored path keeps its shape only: whether the file still works is the host's check, right before it runs.
|
|
249
|
+
if (TOOL_PATH_FIELDS[key]) { const toolPath = cleanToolPath(raw[key]); out[key] = toolPath === undefined ? '' : toolPath; continue; }
|
|
167
250
|
out[key] = raw[key] !== undefined && CHECKS[key](raw[key]) ? raw[key] : USER_DEFAULTS[key];
|
|
168
251
|
}
|
|
169
252
|
out.mode = MODES.includes(raw.mode) ? raw.mode : SERVICE_DEFAULTS.mode;
|
|
@@ -172,7 +255,7 @@ export function effectiveConfig(stored = {}) {
|
|
|
172
255
|
? raw.previousSessionIds.map((id) => (typeof id === 'string' ? id : id?.id)).filter((id) => typeof id === 'string' && id).slice(0, 10)
|
|
173
256
|
: [];
|
|
174
257
|
out.owner = cleanOwner(raw.owner);
|
|
175
|
-
out.autonomousUntil =
|
|
258
|
+
out.autonomousUntil = null; // legacy: Autonomous has no time limit (migrateLegacyAutonomous)
|
|
176
259
|
out.paused = raw.paused === true;
|
|
177
260
|
out.pausedBy = out.paused && (raw.pausedBy === 'desktop' || raw.pausedBy === 'phone') ? raw.pausedBy : null;
|
|
178
261
|
out.version = Number.isSafeInteger(raw.version) && raw.version >= 0 ? raw.version : 0;
|
|
@@ -181,18 +264,23 @@ export function effectiveConfig(stored = {}) {
|
|
|
181
264
|
return out;
|
|
182
265
|
}
|
|
183
266
|
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
267
|
+
/**
|
|
268
|
+
* The configured level ('ask' when it is not a known one). Autonomous has no time limit: it
|
|
269
|
+
* holds until the owner lowers it. A legacy 8-hour Autonomous (migrateLegacyAutonomous) is 'ask'.
|
|
270
|
+
*/
|
|
271
|
+
export function currentLevel(config) {
|
|
272
|
+
if (migrateLegacyAutonomous(config)) return 'ask';
|
|
273
|
+
return LEVELS.includes(config?.level) ? config.level : 'ask';
|
|
188
274
|
}
|
|
189
275
|
|
|
190
276
|
/**
|
|
191
277
|
* @param {object} deps
|
|
192
278
|
* @param {(key:string)=>string|null} deps.get getKvConfig
|
|
193
279
|
* @param {(key:string, value:string)=>void} deps.set setKvConfig
|
|
280
|
+
* @param {() => void} [deps.onLegacyAutonomous] a legacy 8-hour Autonomous was stored as Ask (once, after it was saved)
|
|
281
|
+
* @param {(value:string, kind:'program'|'file') => string|null} [deps.checkPath] how update() checks a tool path on disk
|
|
194
282
|
*/
|
|
195
|
-
export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.now, log = () => {} } = {}) {
|
|
283
|
+
export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.now, log = () => {}, onLegacyAutonomous = null, checkPath = defaultCheckPath } = {}) {
|
|
196
284
|
let cache = null; // { at, value }
|
|
197
285
|
|
|
198
286
|
function readStored() {
|
|
@@ -202,12 +290,34 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
202
290
|
if (!raw) return {};
|
|
203
291
|
try {
|
|
204
292
|
const value = JSON.parse(raw);
|
|
205
|
-
return isPlainObject(value) ? value : {};
|
|
293
|
+
return isPlainObject(value) ? migrateStored(value) : {};
|
|
206
294
|
} catch (error) {
|
|
207
295
|
log('whatsapp:config-corrupt', error?.message || String(error));
|
|
208
296
|
return {};
|
|
209
297
|
}
|
|
210
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* A legacy 8-hour Autonomous (migrateLegacyAutonomous) found by any read is stored as Ask at
|
|
301
|
+
* once, with the version bumped (an open Settings tab reloads before it saves), and reported
|
|
302
|
+
* once. A store that cannot be written still reads it as Ask, and tries again at the next read.
|
|
303
|
+
*/
|
|
304
|
+
function migrateStored(value) {
|
|
305
|
+
const migrated = migrateLegacyAutonomous(value);
|
|
306
|
+
if (!migrated) return value;
|
|
307
|
+
const version = Number.isSafeInteger(value.version) && value.version >= 0 ? value.version : 0;
|
|
308
|
+
const next = { ...migrated, version: version + 1 };
|
|
309
|
+
try {
|
|
310
|
+
if (typeof set !== 'function') return next;
|
|
311
|
+
set(WHATSAPP_CONFIG_KEY, JSON.stringify(next));
|
|
312
|
+
} catch (error) {
|
|
313
|
+
log('whatsapp:config-unwritable', `the earlier Autonomous could not be stored as Ask: ${error?.message || String(error)}`);
|
|
314
|
+
return next;
|
|
315
|
+
}
|
|
316
|
+
cache = null;
|
|
317
|
+
log('whatsapp:config-migrated', 'Autonomous from an earlier version (8-hour window) ended; the level is Ask');
|
|
318
|
+
try { onLegacyAutonomous?.(); } catch {}
|
|
319
|
+
return next;
|
|
320
|
+
}
|
|
211
321
|
function persist(value) {
|
|
212
322
|
if (typeof set === 'function') set(WHATSAPP_CONFIG_KEY, JSON.stringify(value));
|
|
213
323
|
cache = null;
|
|
@@ -221,8 +331,8 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
221
331
|
|
|
222
332
|
/**
|
|
223
333
|
* Service-owned write: service fields, internal state and user fields the
|
|
224
|
-
* service decides (the confirmed autonomous level
|
|
225
|
-
*
|
|
334
|
+
* service decides (the confirmed autonomous level). A change to a user field
|
|
335
|
+
* bumps the version so an open Settings tab reloads first.
|
|
226
336
|
*/
|
|
227
337
|
function write(patch = {}) {
|
|
228
338
|
if (!isPlainObject(patch)) return read();
|
|
@@ -239,6 +349,10 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
239
349
|
if (key === 'version') continue;
|
|
240
350
|
// Computer use is turned on by the person at this computer only (update()); the service may only turn it off.
|
|
241
351
|
if (key === 'computerUse' && value !== false) { log('whatsapp:config-ignored', 'computerUse is turned on in Settings only'); continue; }
|
|
352
|
+
// What the host runs (whisper-cli, FFmpeg) and the model it loads are chosen in Settings only; a service write may clear them.
|
|
353
|
+
if (TOOL_PATH_FIELDS[key] && value !== '' && value !== null) { log('whatsapp:config-ignored', `${key} is set in Settings only`); continue; }
|
|
354
|
+
// A legacy field: Autonomous has no time limit, so nothing writes an expiry (only null, which clears it).
|
|
355
|
+
if (key === 'autonomousUntil' && value !== null) { log('whatsapp:config-ignored', 'autonomousUntil is a legacy field: Autonomous has no time limit'); continue; }
|
|
242
356
|
next[key] = value;
|
|
243
357
|
if (USER_FIELDS.includes(key) && JSON.stringify(effectiveConfig(next)[key]) !== JSON.stringify(before[key])) userChanged = true;
|
|
244
358
|
}
|
|
@@ -253,7 +367,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
253
367
|
* level waits for the phone (the service holds the escalation).
|
|
254
368
|
*/
|
|
255
369
|
function update(patch = {}, { expectedVersion = null, confirmEscalation = false } = {}) {
|
|
256
|
-
const clean = validateConfigPatch(patch);
|
|
370
|
+
const clean = validateConfigPatch(patch, { checkPath });
|
|
257
371
|
const stored = readStored();
|
|
258
372
|
const current = effectiveConfig(stored);
|
|
259
373
|
if (expectedVersion !== null && expectedVersion !== undefined && Number(expectedVersion) !== current.version) {
|
|
@@ -263,7 +377,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
263
377
|
}
|
|
264
378
|
let pending = false;
|
|
265
379
|
if (clean.level !== undefined) {
|
|
266
|
-
const effective = currentLevel(current
|
|
380
|
+
const effective = currentLevel(current);
|
|
267
381
|
if (clean.level === 'autonomous') {
|
|
268
382
|
if (effective !== 'autonomous') {
|
|
269
383
|
if (confirmEscalation !== true) throw configError('level', 'Autonomous needs a second confirmation on this computer, then an ALLOW code from your phone.', { code: 'CONFIRM_REQUIRED' });
|
|
@@ -271,7 +385,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
271
385
|
}
|
|
272
386
|
delete clean.level;
|
|
273
387
|
} else if (RANK[clean.level] < RANK[effective] || clean.level !== current.level) {
|
|
274
|
-
// Lowering
|
|
388
|
+
// Lowering applies at once (and clears the legacy autonomousUntil).
|
|
275
389
|
clean.autonomousUntil = null;
|
|
276
390
|
}
|
|
277
391
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// Fake Baileys runtime for tests and SYNABUN_WHATSAPP_FAKE=1. Exports the same
|
|
2
2
|
// names as connector/entry.mjs, with a socket that implements exactly the
|
|
3
3
|
// surface baileys-adapter.js and host-core.js use, plus `fake`: a controller
|
|
4
|
-
// that plays the phone side (scan, inbound from the owner or a stranger, text
|
|
5
|
-
// or
|
|
6
|
-
//
|
|
4
|
+
// that plays the phone side (scan, inbound from the owner or a stranger, text,
|
|
5
|
+
// image or voice note, disconnect by reason name, forbidden, crash), closes the socket
|
|
6
|
+
// with the errors Baileys 7.0.0-rc14 builds (a stream error, a WebSocket
|
|
7
|
+
// error, a connection failure), can make socket construction throw, and
|
|
8
|
+
// records every outgoing call so tests can assert who the host talked to.
|
|
7
9
|
//
|
|
8
10
|
// Module state is per process: call fake.reset() between tests.
|
|
9
11
|
|
|
10
12
|
import { EventEmitter } from 'node:events';
|
|
11
|
-
import { randomBytes } from 'node:crypto';
|
|
13
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
12
14
|
import { FALLBACK_CODEC } from './auth-store.js';
|
|
13
15
|
|
|
14
16
|
export const BAILEYS_VERSION = 'fake';
|
|
@@ -102,13 +104,17 @@ export function getContentType(content) {
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
export async function downloadMediaMessage(message, _type, _options, ctx) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
if (
|
|
107
|
+
const content = normalizeMessageContent(message?.message);
|
|
108
|
+
const media = content?.imageMessage || content?.audioMessage;
|
|
109
|
+
if (!media) throw new Error('not a media message');
|
|
110
|
+
state.downloads += 1;
|
|
111
|
+
// A download that never answers (the adapter's 30 s timeout).
|
|
112
|
+
if (media.fakeHang) return new Promise(() => {});
|
|
113
|
+
if (media.fakeFail) {
|
|
108
114
|
if (ctx?.reuploadRequest) await ctx.reuploadRequest(message);
|
|
109
115
|
throw Object.assign(new Error('fake download failed'), { status: 404 });
|
|
110
116
|
}
|
|
111
|
-
return Buffer.from(
|
|
117
|
+
return Buffer.from(media.fakeData || []);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
export async function fetchLatestBaileysVersion() {
|
|
@@ -137,6 +143,9 @@ const state = {
|
|
|
137
143
|
ignoredByJid: 0,
|
|
138
144
|
latestVersion: [2, 3000, 1027934701],
|
|
139
145
|
failNextSend: false,
|
|
146
|
+
failSockets: 0, // makeWASocket throws this many more times
|
|
147
|
+
socketFailures: 0,
|
|
148
|
+
downloads: 0, // downloadMediaMessage calls (a refused voice note is never downloaded)
|
|
140
149
|
seq: 0,
|
|
141
150
|
account: { pn: '15550001111', lid: '99887766554433', name: 'Fake Account' },
|
|
142
151
|
people: {
|
|
@@ -145,10 +154,11 @@ const state = {
|
|
|
145
154
|
},
|
|
146
155
|
};
|
|
147
156
|
|
|
148
|
-
function boom(statusCode, message = 'fake disconnect') {
|
|
157
|
+
function boom(statusCode, message = 'fake disconnect', data = undefined) {
|
|
149
158
|
const err = new Error(message);
|
|
150
159
|
err.isBoom = true;
|
|
151
160
|
err.output = { statusCode, payload: { statusCode, message } };
|
|
161
|
+
if (data !== undefined) err.data = data;
|
|
152
162
|
return err;
|
|
153
163
|
}
|
|
154
164
|
|
|
@@ -199,16 +209,32 @@ function makeSocket(config) {
|
|
|
199
209
|
throw new Error('fake send failure');
|
|
200
210
|
}
|
|
201
211
|
const id = `FAKEOUT${++state.seq}`;
|
|
212
|
+
// A file (whatsapp_send_file): what Baileys would upload, recorded by size and hash, never the bytes.
|
|
213
|
+
const media = ['document', 'image', 'video', 'audio'].find((key) => content && Buffer.isBuffer(content[key])) || null;
|
|
202
214
|
state.sent.push({
|
|
203
215
|
jid,
|
|
204
|
-
kind: content?.react ? 'react' : content?.text !== undefined ? 'text' : 'other',
|
|
216
|
+
kind: content?.react ? 'react' : content?.text !== undefined ? 'text' : media || 'other',
|
|
205
217
|
text: content?.text,
|
|
206
218
|
react: content?.react ? { text: content.react.text, keyId: content.react.key?.id, keyJid: content.react.key?.remoteJid } : undefined,
|
|
207
219
|
linkPreview: content?.linkPreview,
|
|
208
220
|
quotedId: options?.quoted?.key?.id ?? null,
|
|
221
|
+
...(media ? {
|
|
222
|
+
file: {
|
|
223
|
+
bytes: content[media].length,
|
|
224
|
+
sha256: createHash('sha256').update(content[media]).digest('hex'),
|
|
225
|
+
mimetype: content.mimetype ?? null,
|
|
226
|
+
fileName: content.fileName ?? null,
|
|
227
|
+
caption: content.caption ?? null,
|
|
228
|
+
ptt: content.ptt ?? null,
|
|
229
|
+
keys: Object.keys(content).sort(),
|
|
230
|
+
jpegThumbnail: Object.hasOwn(content, 'jpegThumbnail') ? content.jpegThumbnail : 'absent',
|
|
231
|
+
},
|
|
232
|
+
} : {}),
|
|
209
233
|
at: Date.now(),
|
|
210
234
|
});
|
|
211
|
-
const message = content?.react ? { reactionMessage: content.react }
|
|
235
|
+
const message = content?.react ? { reactionMessage: content.react }
|
|
236
|
+
: media ? { [`${media}Message`]: { mimetype: content.mimetype ?? null, fileLength: content[media].length, ...(content.fileName ? { fileName: content.fileName } : {}), ...(content.caption ? { caption: content.caption } : {}) } }
|
|
237
|
+
: { extendedTextMessage: { text: content?.text ?? '' } };
|
|
212
238
|
return { key: { remoteJid: jid, fromMe: true, id }, message, messageTimestamp: Math.floor(Date.now() / 1000) };
|
|
213
239
|
},
|
|
214
240
|
requestPairingCode: async (phone) => {
|
|
@@ -263,6 +289,11 @@ function makeSocket(config) {
|
|
|
263
289
|
}
|
|
264
290
|
|
|
265
291
|
export function makeWASocket(config) {
|
|
292
|
+
if (state.failSockets > 0) {
|
|
293
|
+
state.failSockets -= 1;
|
|
294
|
+
state.socketFailures += 1;
|
|
295
|
+
throw new Error('fake socket construction failure');
|
|
296
|
+
}
|
|
266
297
|
const sock = makeSocket(config);
|
|
267
298
|
state.sockets.push(sock);
|
|
268
299
|
return sock;
|
|
@@ -274,13 +305,32 @@ function current() {
|
|
|
274
305
|
return sock;
|
|
275
306
|
}
|
|
276
307
|
|
|
277
|
-
function buildContent({ text, image, forwarded, quoted }) {
|
|
308
|
+
function buildContent({ text, image, audio, forwarded, quoted }) {
|
|
278
309
|
const contextInfo = forwarded || quoted
|
|
279
310
|
? {
|
|
280
311
|
...(forwarded ? { isForwarded: true, forwardingScore: 1 } : {}),
|
|
281
312
|
...(quoted ? { stanzaId: quoted.id, quotedMessage: { conversation: quoted.text ?? '' } } : {}),
|
|
282
313
|
}
|
|
283
314
|
: undefined;
|
|
315
|
+
if (audio) {
|
|
316
|
+
// A voice note (ptt, OGG/Opus) or an audio file; `seconds` / `fileLength` are what the message declares
|
|
317
|
+
// (null leaves it out). A caption is not something WhatsApp offers on audio: `text` plays one anyway.
|
|
318
|
+
const data = Buffer.isBuffer(audio.data) ? audio.data : Buffer.from(audio.base64 || '', 'base64');
|
|
319
|
+
return {
|
|
320
|
+
audioMessage: {
|
|
321
|
+
mimetype: audio.mimetype || 'audio/ogg; codecs=opus',
|
|
322
|
+
ptt: audio.ptt !== false,
|
|
323
|
+
...(audio.seconds === null ? {} : { seconds: audio.seconds ?? 3 }),
|
|
324
|
+
...(audio.fileLength === null ? {} : { fileLength: audio.fileLength ?? data.length }),
|
|
325
|
+
...(text ? { caption: text } : {}),
|
|
326
|
+
url: 'https://fake.invalid/media',
|
|
327
|
+
fakeData: data,
|
|
328
|
+
fakeFail: !!audio.fail,
|
|
329
|
+
fakeHang: !!audio.hang,
|
|
330
|
+
...(contextInfo ? { contextInfo } : {}),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
284
334
|
if (image) {
|
|
285
335
|
const data = Buffer.isBuffer(image.data) ? image.data : Buffer.from(image.base64 || '', 'base64');
|
|
286
336
|
return {
|
|
@@ -315,6 +365,9 @@ export const fake = {
|
|
|
315
365
|
state.versionFetches = 0;
|
|
316
366
|
state.ignoredByJid = 0;
|
|
317
367
|
state.failNextSend = false;
|
|
368
|
+
state.failSockets = 0;
|
|
369
|
+
state.socketFailures = 0;
|
|
370
|
+
state.downloads = 0;
|
|
318
371
|
state.latestVersion = [2, 3000, 1027934701];
|
|
319
372
|
state.seq = 0;
|
|
320
373
|
state.account = { pn: '15550001111', lid: '99887766554433', name: 'Fake Account' };
|
|
@@ -346,7 +399,7 @@ export const fake = {
|
|
|
346
399
|
* (epoch ms, default now) is when the phone sent it.
|
|
347
400
|
*/
|
|
348
401
|
inbound({
|
|
349
|
-
from = 'self', text, image = null, device = 0, type = 'notify', addressing = 'pn', id, forwarded = false, quoted = null,
|
|
402
|
+
from = 'self', text, image = null, audio = null, device = 0, type = 'notify', addressing = 'pn', id, forwarded = false, quoted = null,
|
|
350
403
|
requestId, noStanza = false, stanzaFrom, content, stub, at = null, offline = null,
|
|
351
404
|
} = {}) {
|
|
352
405
|
const sock = current();
|
|
@@ -364,7 +417,7 @@ export const fake = {
|
|
|
364
417
|
key: { remoteJid: chatJid, remoteJidAlt: fromMe ? undefined : chatAlt, fromMe, id: msgId, addressingMode: lid ? 'lid' : 'pn' },
|
|
365
418
|
messageTimestamp: ts,
|
|
366
419
|
pushName: 'fake',
|
|
367
|
-
message: stub ? undefined : content || buildContent({ text, image, forwarded, quoted }),
|
|
420
|
+
message: stub ? undefined : content || buildContent({ text, image, audio, forwarded, quoted }),
|
|
368
421
|
...(stub ? { messageStubType: 1 } : {}),
|
|
369
422
|
};
|
|
370
423
|
const stanzaSender = stanzaFrom || senderJid;
|
|
@@ -395,6 +448,32 @@ export const fake = {
|
|
|
395
448
|
forbidden() {
|
|
396
449
|
return fake.disconnect('forbidden');
|
|
397
450
|
},
|
|
451
|
+
/**
|
|
452
|
+
* A <stream:error> the way Baileys rc14 closes on it (getErrorCodeFromStreamError + the
|
|
453
|
+
* CB:stream:error handler): "Stream Errored (<first child tag>)", the code from the node's
|
|
454
|
+
* own `code` attribute, else 440 for <conflict>, else 500; the child node in `data`.
|
|
455
|
+
*/
|
|
456
|
+
streamError({ tag = 'unknown', code = null } = {}) {
|
|
457
|
+
const statusCode = Number(code) || (tag === 'conflict' ? DisconnectReason.connectionReplaced : DisconnectReason.badSession);
|
|
458
|
+
const reason = statusCode === DisconnectReason.restartRequired ? 'restart required' : tag;
|
|
459
|
+
current().end(boom(statusCode, `Stream Errored (${reason})`, { tag, attrs: code ? { code: String(code) } : {} }));
|
|
460
|
+
return { ok: true };
|
|
461
|
+
},
|
|
462
|
+
/** A WebSocket error the way mapWebSocketError wraps it: 500 unless getCodeFromWSError places it. */
|
|
463
|
+
wsError({ message = 'socket hang up', code = 500 } = {}) {
|
|
464
|
+
current().end(boom(Number(code) || 500, `WebSocket Error (${message})`, { message }));
|
|
465
|
+
return { ok: true };
|
|
466
|
+
},
|
|
467
|
+
/** A <failure> node (CB:failure): "Connection Failure", its `reason` attribute or 500. */
|
|
468
|
+
connectionFailure({ reason = null } = {}) {
|
|
469
|
+
current().end(boom(Number(reason) || 500, 'Connection Failure', reason ? { reason: String(reason) } : {}));
|
|
470
|
+
return { ok: true };
|
|
471
|
+
},
|
|
472
|
+
/** The next `n` makeWASocket calls throw (a socket that cannot even be built). */
|
|
473
|
+
failNextSocket(n = 1) {
|
|
474
|
+
state.failSockets = Math.max(0, Number(n) || 0);
|
|
475
|
+
return { ok: true };
|
|
476
|
+
},
|
|
398
477
|
crash() {
|
|
399
478
|
setImmediate(() => { throw new Error('fake runtime crash'); });
|
|
400
479
|
return { ok: true };
|
|
@@ -412,6 +491,8 @@ export const fake = {
|
|
|
412
491
|
reads: state.reads.map((r) => ({ ...r })),
|
|
413
492
|
pairingRequests: [...state.pairingRequests],
|
|
414
493
|
logouts: state.logouts,
|
|
494
|
+
socketFailures: state.socketFailures,
|
|
495
|
+
downloads: state.downloads,
|
|
415
496
|
versionFetches: state.versionFetches,
|
|
416
497
|
ignoredByJid: state.ignoredByJid,
|
|
417
498
|
account: { ...state.account },
|
|
@@ -427,6 +508,10 @@ export const fake = {
|
|
|
427
508
|
inbound: () => fake.inbound(args),
|
|
428
509
|
disconnect: () => fake.disconnect(args.reason),
|
|
429
510
|
forbidden: () => fake.forbidden(),
|
|
511
|
+
streamError: () => fake.streamError(args),
|
|
512
|
+
wsError: () => fake.wsError(args),
|
|
513
|
+
connectionFailure: () => fake.connectionFailure(args),
|
|
514
|
+
failNextSocket: () => fake.failNextSocket(args.n ?? 1),
|
|
430
515
|
crash: () => fake.crash(),
|
|
431
516
|
failNextSend: () => fake.failNextSend(),
|
|
432
517
|
snapshot: () => fake.snapshot(),
|