@moxxy/plugin-channel-whatsapp 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/auth-state.d.ts +66 -0
- package/dist/auth-state.d.ts.map +1 -0
- package/dist/auth-state.js +99 -0
- package/dist/auth-state.js.map +1 -0
- package/dist/baileys-socket.d.ts +10 -0
- package/dist/baileys-socket.d.ts.map +1 -0
- package/dist/baileys-socket.js +93 -0
- package/dist/baileys-socket.js.map +1 -0
- package/dist/channel/turn-runner.d.ts +58 -0
- package/dist/channel/turn-runner.d.ts.map +1 -0
- package/dist/channel/turn-runner.js +130 -0
- package/dist/channel/turn-runner.js.map +1 -0
- package/dist/channel/voice-handler.d.ts +27 -0
- package/dist/channel/voice-handler.d.ts.map +1 -0
- package/dist/channel/voice-handler.js +55 -0
- package/dist/channel/voice-handler.js.map +1 -0
- package/dist/channel.d.ts +105 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +459 -0
- package/dist/channel.js.map +1 -0
- package/dist/consent-prompt.d.ts +9 -0
- package/dist/consent-prompt.d.ts.map +1 -0
- package/dist/consent-prompt.js +28 -0
- package/dist/consent-prompt.js.map +1 -0
- package/dist/consent.d.ts +22 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/consent.js +40 -0
- package/dist/consent.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +211 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +39 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +86 -0
- package/dist/keys.js.map +1 -0
- package/dist/message-gate.d.ts +50 -0
- package/dist/message-gate.d.ts.map +1 -0
- package/dist/message-gate.js +150 -0
- package/dist/message-gate.js.map +1 -0
- package/dist/pair-flow.d.ts +12 -0
- package/dist/pair-flow.d.ts.map +1 -0
- package/dist/pair-flow.js +120 -0
- package/dist/pair-flow.js.map +1 -0
- package/dist/permission.d.ts +29 -0
- package/dist/permission.d.ts.map +1 -0
- package/dist/permission.js +78 -0
- package/dist/permission.js.map +1 -0
- package/dist/setup-wizard.d.ts +13 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +127 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/socket.d.ts +72 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +22 -0
- package/dist/socket.js.map +1 -0
- package/package.json +99 -0
- package/src/auth-state.test.ts +103 -0
- package/src/auth-state.ts +161 -0
- package/src/baileys-socket.ts +154 -0
- package/src/channel/turn-runner.test.ts +31 -0
- package/src/channel/turn-runner.ts +156 -0
- package/src/channel/voice-handler.ts +83 -0
- package/src/channel.test.ts +344 -0
- package/src/channel.ts +571 -0
- package/src/consent-prompt.ts +28 -0
- package/src/consent.test.ts +56 -0
- package/src/consent.ts +48 -0
- package/src/index.ts +284 -0
- package/src/keys.test.ts +59 -0
- package/src/keys.ts +78 -0
- package/src/message-gate.test.ts +121 -0
- package/src/message-gate.ts +184 -0
- package/src/pair-flow.ts +133 -0
- package/src/permission.test.ts +94 -0
- package/src/permission.ts +114 -0
- package/src/setup-wizard.ts +162 -0
- package/src/socket.test.ts +17 -0
- package/src/socket.ts +89 -0
- package/src/subcommands.test.ts +198 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baileys auth-state persistence behind a swappable storage adapter.
|
|
3
|
+
*
|
|
4
|
+
* Baileys' multi-device auth is a ROTATING key store (signal sessions, pre-keys,
|
|
5
|
+
* sender keys, app-state sync keys) plus a creds record — dozens of small files
|
|
6
|
+
* that churn constantly. That churn is why the moxxy VAULT is the wrong home for
|
|
7
|
+
* it (the vault is an encrypted, tagged, human-curated secret store, not a
|
|
8
|
+
* high-write key-value backend); instead the state lives in a dedicated
|
|
9
|
+
* directory under the moxxy home with tight modes (dir 0700, files 0600).
|
|
10
|
+
* TRADE-OFF (recorded in TECH_DEBT + the PR): unlike vault entries, these files
|
|
11
|
+
* are NOT encrypted at rest — anyone with file access to the moxxy home can
|
|
12
|
+
* hijack the linked WhatsApp session. The {@link WhatsAppAuthStorage} interface
|
|
13
|
+
* exists precisely so an encrypted backend can be swapped in later without
|
|
14
|
+
* touching the channel.
|
|
15
|
+
*/
|
|
16
|
+
export interface WhatsAppAuthStorage {
|
|
17
|
+
read(key: string): Promise<string | null>;
|
|
18
|
+
write(key: string, value: string): Promise<void>;
|
|
19
|
+
delete(key: string): Promise<void>;
|
|
20
|
+
/** Wipe the whole store (logout / unpair). */
|
|
21
|
+
clear(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** Signal key ids contain `/` and `:`; map every key to one safe flat filename. */
|
|
24
|
+
export declare function sanitizeAuthKey(key: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* The default storage backend: one JSON file per key under `dir`, created 0700
|
|
27
|
+
* with 0600 files so the signal identity is at least owner-only on disk.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createFileAuthStorage(dir: string): WhatsAppAuthStorage;
|
|
30
|
+
/** True when a linked-device credential record exists (i.e. paired before). */
|
|
31
|
+
export declare function hasStoredCreds(storage: WhatsAppAuthStorage): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* The tiny slice of Baileys this module needs — injected so unit tests never
|
|
34
|
+
* import the real package, and so the production wiring (`baileys-socket.ts`)
|
|
35
|
+
* can hand in the lazily-imported module.
|
|
36
|
+
*/
|
|
37
|
+
export interface BaileysAuthBridge {
|
|
38
|
+
initAuthCreds(): Record<string, unknown>;
|
|
39
|
+
readonly BufferJSON: {
|
|
40
|
+
readonly reviver: (key: string, value: unknown) => unknown;
|
|
41
|
+
readonly replacer: (key: string, value: unknown) => unknown;
|
|
42
|
+
};
|
|
43
|
+
/** `proto.Message.AppStateSyncKeyData.fromObject` — revives that one typed key
|
|
44
|
+
* category the way Baileys' own `useMultiFileAuthState` does. Optional so a
|
|
45
|
+
* fake bridge in tests can skip it. */
|
|
46
|
+
readonly reviveAppStateSyncKey?: (value: unknown) => unknown;
|
|
47
|
+
}
|
|
48
|
+
export interface WhatsAppAuthState {
|
|
49
|
+
/** Shaped exactly like Baileys' `AuthenticationState` (creds + keys store). */
|
|
50
|
+
readonly state: {
|
|
51
|
+
readonly creds: Record<string, unknown>;
|
|
52
|
+
readonly keys: {
|
|
53
|
+
get(type: string, ids: string[]): Promise<Record<string, unknown>>;
|
|
54
|
+
set(data: Record<string, Record<string, unknown | null>>): Promise<void>;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/** Persist the creds record; wire to the socket's `creds.update` event. */
|
|
58
|
+
saveCreds(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `useMultiFileAuthState` equivalent over a {@link WhatsAppAuthStorage} — the
|
|
62
|
+
* same creds + `<type>-<id>` key layout, serialized with Baileys' BufferJSON
|
|
63
|
+
* (signal keys are raw byte arrays), but with the storage backend swappable.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createWhatsAppAuthState(storage: WhatsAppAuthStorage, bridge: BaileysAuthBridge): Promise<WhatsAppAuthState>;
|
|
66
|
+
//# sourceMappingURL=auth-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-state.d.ts","sourceRoot":"","sources":["../src/auth-state.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,8CAA8C;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CA8BtE;AAKD,+EAA+E;AAC/E,wBAAsB,cAAc,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnF;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;QAC3D,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;KAC7D,CAAC;IACF;;4CAEwC;IACxC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;CAC9D;AAED,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxC,QAAQ,CAAC,IAAI,EAAE;YACb,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;YACnE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;SAC1E,CAAC;KACH,CAAC;IACF,2EAA2E;IAC3E,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,mBAAmB,EAC5B,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CA+C5B"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/** Signal key ids contain `/` and `:`; map every key to one safe flat filename. */
|
|
4
|
+
export function sanitizeAuthKey(key) {
|
|
5
|
+
return key.replace(/[^a-zA-Z0-9._-]/g, '_').replace(/^\.+/, '_');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The default storage backend: one JSON file per key under `dir`, created 0700
|
|
9
|
+
* with 0600 files so the signal identity is at least owner-only on disk.
|
|
10
|
+
*/
|
|
11
|
+
export function createFileAuthStorage(dir) {
|
|
12
|
+
const fileFor = (key) => path.join(dir, `${sanitizeAuthKey(key)}.json`);
|
|
13
|
+
const ensureDir = async () => {
|
|
14
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
async read(key) {
|
|
18
|
+
try {
|
|
19
|
+
return await fs.readFile(fileFor(key), 'utf8');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
async write(key, value) {
|
|
26
|
+
await ensureDir();
|
|
27
|
+
const target = fileFor(key);
|
|
28
|
+
// Write-then-rename so a crash mid-write can't truncate the creds record;
|
|
29
|
+
// chmod explicitly because writeFile's mode option is masked by umask.
|
|
30
|
+
const tmp = `${target}.tmp`;
|
|
31
|
+
await fs.writeFile(tmp, value, { mode: 0o600 });
|
|
32
|
+
await fs.chmod(tmp, 0o600);
|
|
33
|
+
await fs.rename(tmp, target);
|
|
34
|
+
},
|
|
35
|
+
async delete(key) {
|
|
36
|
+
await fs.rm(fileFor(key), { force: true });
|
|
37
|
+
},
|
|
38
|
+
async clear() {
|
|
39
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** The creds record's storage key (every other key is `<type>-<id>`). */
|
|
44
|
+
const CREDS_KEY = 'creds';
|
|
45
|
+
/** True when a linked-device credential record exists (i.e. paired before). */
|
|
46
|
+
export async function hasStoredCreds(storage) {
|
|
47
|
+
return (await storage.read(CREDS_KEY)) != null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `useMultiFileAuthState` equivalent over a {@link WhatsAppAuthStorage} — the
|
|
51
|
+
* same creds + `<type>-<id>` key layout, serialized with Baileys' BufferJSON
|
|
52
|
+
* (signal keys are raw byte arrays), but with the storage backend swappable.
|
|
53
|
+
*/
|
|
54
|
+
export async function createWhatsAppAuthState(storage, bridge) {
|
|
55
|
+
const readJson = async (key) => {
|
|
56
|
+
const raw = await storage.read(key);
|
|
57
|
+
if (raw == null)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(raw, bridge.BufferJSON.reviver);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const writeJson = (key, value) => storage.write(key, JSON.stringify(value, bridge.BufferJSON.replacer));
|
|
67
|
+
const creds = (await readJson(CREDS_KEY)) ?? bridge.initAuthCreds();
|
|
68
|
+
return {
|
|
69
|
+
state: {
|
|
70
|
+
creds,
|
|
71
|
+
keys: {
|
|
72
|
+
async get(type, ids) {
|
|
73
|
+
const data = {};
|
|
74
|
+
await Promise.all(ids.map(async (id) => {
|
|
75
|
+
let value = await readJson(`${type}-${id}`);
|
|
76
|
+
if (type === 'app-state-sync-key' && value != null && bridge.reviveAppStateSyncKey) {
|
|
77
|
+
value = bridge.reviveAppStateSyncKey(value);
|
|
78
|
+
}
|
|
79
|
+
if (value != null)
|
|
80
|
+
data[id] = value;
|
|
81
|
+
}));
|
|
82
|
+
return data;
|
|
83
|
+
},
|
|
84
|
+
async set(data) {
|
|
85
|
+
const tasks = [];
|
|
86
|
+
for (const [category, entries] of Object.entries(data)) {
|
|
87
|
+
for (const [id, value] of Object.entries(entries)) {
|
|
88
|
+
const key = `${category}-${id}`;
|
|
89
|
+
tasks.push(value == null ? storage.delete(key) : writeJson(key, value));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
await Promise.all(tasks);
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
saveCreds: () => writeJson(CREDS_KEY, creds),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=auth-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-state.js","sourceRoot":"","sources":["../src/auth-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WhatsAppSocketFactory } from './socket.js';
|
|
2
|
+
/**
|
|
3
|
+
* The production {@link WhatsAppSocketFactory}: lazily imports Baileys (a heavy
|
|
4
|
+
* dependency tree — protobufjs, libsignal — that must NOT load at plugin
|
|
5
|
+
* discovery time, only when the channel actually starts; same lesson as the
|
|
6
|
+
* desktop-host eager-import boot crash), builds the swappable auth state, and
|
|
7
|
+
* adapts the socket down to the narrow contract the channel uses.
|
|
8
|
+
*/
|
|
9
|
+
export declare const openBaileysSocket: WhatsAppSocketFactory;
|
|
10
|
+
//# sourceMappingURL=baileys-socket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baileys-socket.d.ts","sourceRoot":"","sources":["../src/baileys-socket.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAMV,qBAAqB,EAEtB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,qBAyC/B,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createWhatsAppAuthState } from './auth-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* The production {@link WhatsAppSocketFactory}: lazily imports Baileys (a heavy
|
|
4
|
+
* dependency tree — protobufjs, libsignal — that must NOT load at plugin
|
|
5
|
+
* discovery time, only when the channel actually starts; same lesson as the
|
|
6
|
+
* desktop-host eager-import boot crash), builds the swappable auth state, and
|
|
7
|
+
* adapts the socket down to the narrow contract the channel uses.
|
|
8
|
+
*/
|
|
9
|
+
export const openBaileysSocket = async ({ storage, logger }) => {
|
|
10
|
+
const baileys = (await import('@whiskeysockets/baileys'));
|
|
11
|
+
const makeWASocket = baileys.makeWASocket ?? baileys.default?.makeWASocket ?? baileys.default;
|
|
12
|
+
if (typeof makeWASocket !== 'function') {
|
|
13
|
+
throw new Error('@whiskeysockets/baileys: could not resolve makeWASocket export');
|
|
14
|
+
}
|
|
15
|
+
const proto = baileys.proto ?? baileys.default?.proto;
|
|
16
|
+
const bridge = {
|
|
17
|
+
initAuthCreds: baileys.initAuthCreds,
|
|
18
|
+
BufferJSON: baileys.BufferJSON,
|
|
19
|
+
...(proto?.Message?.AppStateSyncKeyData
|
|
20
|
+
? {
|
|
21
|
+
reviveAppStateSyncKey: (value) => proto.Message.AppStateSyncKeyData.fromObject(value),
|
|
22
|
+
}
|
|
23
|
+
: {}),
|
|
24
|
+
};
|
|
25
|
+
const { state, saveCreds } = await createWhatsAppAuthState(storage, bridge);
|
|
26
|
+
const sock = makeWASocket({
|
|
27
|
+
auth: state,
|
|
28
|
+
// The channel renders the QR itself (terminal QR / desktop panel status file).
|
|
29
|
+
printQRInTerminal: false,
|
|
30
|
+
logger: silentBaileysLogger(),
|
|
31
|
+
// Don't mark the linked device "online": keeps phone notifications working
|
|
32
|
+
// and keeps this client's footprint minimal (it is a bot, not a presence).
|
|
33
|
+
markOnlineOnConnect: false,
|
|
34
|
+
// No history backfill — the channel only reacts to live inbound messages;
|
|
35
|
+
// syncing message history would be both wasteful and a privacy liability.
|
|
36
|
+
syncFullHistory: false,
|
|
37
|
+
shouldSyncHistoryMessage: () => false,
|
|
38
|
+
generateHighQualityLinkPreview: false,
|
|
39
|
+
browser: ['moxxy', 'Desktop', '1.0.0'],
|
|
40
|
+
});
|
|
41
|
+
sock.ev.on('creds.update', () => {
|
|
42
|
+
void saveCreds().catch((err) => {
|
|
43
|
+
logger?.warn?.('whatsapp: failed to persist creds update', { err: String(err) });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
return adaptBaileysSocket(sock, baileys, logger);
|
|
47
|
+
};
|
|
48
|
+
function adaptBaileysSocket(sock, baileys, logger) {
|
|
49
|
+
const downloadMediaMessage = baileys.downloadMediaMessage ?? baileys.default?.downloadMediaMessage;
|
|
50
|
+
return {
|
|
51
|
+
userJid: () => sock.user?.id ?? null,
|
|
52
|
+
onConnectionUpdate: (cb) => sock.ev.on('connection.update', (u) => cb(u)),
|
|
53
|
+
onMessages: (cb) => sock.ev.on('messages.upsert', (u) => cb(u)),
|
|
54
|
+
sendText: async (jid, text) => {
|
|
55
|
+
const sent = (await sock.sendMessage(jid, { text }));
|
|
56
|
+
return sent?.key ? { key: sent.key } : null;
|
|
57
|
+
},
|
|
58
|
+
editText: async (jid, key, text) => {
|
|
59
|
+
await sock.sendMessage(jid, { text, edit: key });
|
|
60
|
+
},
|
|
61
|
+
downloadMedia: async (message) => {
|
|
62
|
+
if (typeof downloadMediaMessage !== 'function') {
|
|
63
|
+
throw new Error('@whiskeysockets/baileys: downloadMediaMessage export missing');
|
|
64
|
+
}
|
|
65
|
+
const buf = (await downloadMediaMessage(message, 'buffer', {}, { logger: silentBaileysLogger(), reuploadRequest: sock.updateMediaMessage }));
|
|
66
|
+
return new Uint8Array(buf);
|
|
67
|
+
},
|
|
68
|
+
end: () => {
|
|
69
|
+
try {
|
|
70
|
+
sock.end(undefined);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
logger?.debug?.('whatsapp: socket end threw', { err: String(err) });
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Minimal Baileys ILogger that swallows everything (moxxy has its own logger). */
|
|
79
|
+
function silentBaileysLogger() {
|
|
80
|
+
const noop = () => undefined;
|
|
81
|
+
const self = {
|
|
82
|
+
level: 'silent',
|
|
83
|
+
trace: noop,
|
|
84
|
+
debug: noop,
|
|
85
|
+
info: noop,
|
|
86
|
+
warn: noop,
|
|
87
|
+
error: noop,
|
|
88
|
+
fatal: noop,
|
|
89
|
+
};
|
|
90
|
+
self.child = () => self;
|
|
91
|
+
return self;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=baileys-socket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baileys-socket.js","sourceRoot":"","sources":["../src/baileys-socket.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { newTurnId } from '@moxxy/core';
|
|
2
|
+
import type { ClientSession as Session } from '@moxxy/sdk';
|
|
3
|
+
import type { WaMessageKey, WhatsAppSocket } from '../socket.js';
|
|
4
|
+
/**
|
|
5
|
+
* Streaming strategy (justified against the Baileys API):
|
|
6
|
+
*
|
|
7
|
+
* WhatsApp supports true message edits (a MESSAGE_EDIT protocol send —
|
|
8
|
+
* `sendMessage(jid, { text, edit: key })`, stable in Baileys since 6.5), so the
|
|
9
|
+
* channel streams the way Telegram/Slack do: send ONE message on the first
|
|
10
|
+
* frame, then edit it in place — the user watches a single message grow instead
|
|
11
|
+
* of a flood of partials that can't be assembled. Two WhatsApp-specific
|
|
12
|
+
* adjustments:
|
|
13
|
+
*
|
|
14
|
+
* - `editFrameMs` defaults to 3000 (vs 1000 elsewhere): every edit is a full
|
|
15
|
+
* outbound protocol message, and high-frequency automated edits are exactly
|
|
16
|
+
* the anomalous traffic that gets numbers flagged on an UNOFFICIAL client.
|
|
17
|
+
* Fewer, chunkier edits keep the visible behavior close to a human editing
|
|
18
|
+
* a message. (WhatsApp's ~15-minute edit window is irrelevant at turn scale.)
|
|
19
|
+
*
|
|
20
|
+
* - overflow splitting happens ONLY on the final frame: while streaming, the
|
|
21
|
+
* frame is truncated to {@link WHATSAPP_MAX_MESSAGE_CHARS}; the final flush
|
|
22
|
+
* edits the streamed message to the first chunk and sends the tail chunks as
|
|
23
|
+
* follow-up messages (mirrors Telegram's split-tails). If a FINAL edit fails
|
|
24
|
+
* (edit rejected / message gone), the sink falls back to sending the chunks
|
|
25
|
+
* as new messages so the reply is never silently lost.
|
|
26
|
+
*/
|
|
27
|
+
export declare const WHATSAPP_MAX_MESSAGE_CHARS = 4000;
|
|
28
|
+
export declare const DEFAULT_EDIT_FRAME_MS = 3000;
|
|
29
|
+
/** Split text into ≤maxLen chunks, preferring newline then space boundaries. */
|
|
30
|
+
export declare function splitWhatsAppText(text: string, maxLen?: number): string[];
|
|
31
|
+
export interface TurnRunnerLogger {
|
|
32
|
+
warn?(msg: string, meta?: Record<string, unknown>): void;
|
|
33
|
+
}
|
|
34
|
+
export interface RunWhatsAppTurnDeps {
|
|
35
|
+
readonly session: Session;
|
|
36
|
+
readonly socket: WhatsAppSocket;
|
|
37
|
+
readonly editFrameMs: number;
|
|
38
|
+
/** Record an outbound message id (echo/loop protection in the gate). */
|
|
39
|
+
readonly recordSentId: (key: WaMessageKey | null | undefined) => void;
|
|
40
|
+
readonly logger?: TurnRunnerLogger;
|
|
41
|
+
}
|
|
42
|
+
export interface RunWhatsAppTurnOptions {
|
|
43
|
+
readonly jid: string;
|
|
44
|
+
readonly text: string;
|
|
45
|
+
readonly model?: string | undefined;
|
|
46
|
+
readonly controller: AbortController;
|
|
47
|
+
/** Pre-minted turn id; the channel records it as an own-turn id (#8). */
|
|
48
|
+
readonly turnId: ReturnType<typeof newTurnId>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Drive a single WhatsApp turn end-to-end: subscribe the frame pump to THIS
|
|
52
|
+
* turn's events (turnId-filtered — `session.log` fans out to every listener, so
|
|
53
|
+
* a concurrent turn on the same Session would otherwise stream into this chat,
|
|
54
|
+
* AGENTS.md invariant #8), run the turn, flush the final frame, unwind in
|
|
55
|
+
* `finally`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function runWhatsAppTurn(deps: RunWhatsAppTurnDeps, opts: RunWhatsAppTurnOptions): Promise<void>;
|
|
58
|
+
//# sourceMappingURL=turn-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-runner.d.ts","sourceRoot":"","sources":["../../src/channel/turn-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAC/C,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,gFAAgF;AAChF,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAmC,GAC1C,MAAM,EAAE,CAcV;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;CAC/C;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAwEf"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { FramePump, PlainTurnRenderer, driveTurn, subscribeTurn } from '@moxxy/channel-kit';
|
|
2
|
+
/**
|
|
3
|
+
* Streaming strategy (justified against the Baileys API):
|
|
4
|
+
*
|
|
5
|
+
* WhatsApp supports true message edits (a MESSAGE_EDIT protocol send —
|
|
6
|
+
* `sendMessage(jid, { text, edit: key })`, stable in Baileys since 6.5), so the
|
|
7
|
+
* channel streams the way Telegram/Slack do: send ONE message on the first
|
|
8
|
+
* frame, then edit it in place — the user watches a single message grow instead
|
|
9
|
+
* of a flood of partials that can't be assembled. Two WhatsApp-specific
|
|
10
|
+
* adjustments:
|
|
11
|
+
*
|
|
12
|
+
* - `editFrameMs` defaults to 3000 (vs 1000 elsewhere): every edit is a full
|
|
13
|
+
* outbound protocol message, and high-frequency automated edits are exactly
|
|
14
|
+
* the anomalous traffic that gets numbers flagged on an UNOFFICIAL client.
|
|
15
|
+
* Fewer, chunkier edits keep the visible behavior close to a human editing
|
|
16
|
+
* a message. (WhatsApp's ~15-minute edit window is irrelevant at turn scale.)
|
|
17
|
+
*
|
|
18
|
+
* - overflow splitting happens ONLY on the final frame: while streaming, the
|
|
19
|
+
* frame is truncated to {@link WHATSAPP_MAX_MESSAGE_CHARS}; the final flush
|
|
20
|
+
* edits the streamed message to the first chunk and sends the tail chunks as
|
|
21
|
+
* follow-up messages (mirrors Telegram's split-tails). If a FINAL edit fails
|
|
22
|
+
* (edit rejected / message gone), the sink falls back to sending the chunks
|
|
23
|
+
* as new messages so the reply is never silently lost.
|
|
24
|
+
*/
|
|
25
|
+
export const WHATSAPP_MAX_MESSAGE_CHARS = 4000;
|
|
26
|
+
export const DEFAULT_EDIT_FRAME_MS = 3000;
|
|
27
|
+
/** Split text into ≤maxLen chunks, preferring newline then space boundaries. */
|
|
28
|
+
export function splitWhatsAppText(text, maxLen = WHATSAPP_MAX_MESSAGE_CHARS) {
|
|
29
|
+
if (text.length <= maxLen)
|
|
30
|
+
return [text];
|
|
31
|
+
const chunks = [];
|
|
32
|
+
let rest = text;
|
|
33
|
+
while (rest.length > maxLen) {
|
|
34
|
+
const window = rest.slice(0, maxLen);
|
|
35
|
+
let cut = window.lastIndexOf('\n');
|
|
36
|
+
if (cut < maxLen * 0.5)
|
|
37
|
+
cut = window.lastIndexOf(' ');
|
|
38
|
+
if (cut < maxLen * 0.5)
|
|
39
|
+
cut = maxLen;
|
|
40
|
+
chunks.push(rest.slice(0, cut));
|
|
41
|
+
rest = rest.slice(cut).replace(/^[\n ]/, '');
|
|
42
|
+
}
|
|
43
|
+
if (rest.length > 0)
|
|
44
|
+
chunks.push(rest);
|
|
45
|
+
return chunks;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Drive a single WhatsApp turn end-to-end: subscribe the frame pump to THIS
|
|
49
|
+
* turn's events (turnId-filtered — `session.log` fans out to every listener, so
|
|
50
|
+
* a concurrent turn on the same Session would otherwise stream into this chat,
|
|
51
|
+
* AGENTS.md invariant #8), run the turn, flush the final frame, unwind in
|
|
52
|
+
* `finally`.
|
|
53
|
+
*/
|
|
54
|
+
export async function runWhatsAppTurn(deps, opts) {
|
|
55
|
+
const { session, socket, editFrameMs, recordSentId, logger } = deps;
|
|
56
|
+
const { jid, text, model, controller, turnId } = opts;
|
|
57
|
+
const renderer = new PlainTurnRenderer();
|
|
58
|
+
const sendChunks = async (chunks) => {
|
|
59
|
+
let firstKey = null;
|
|
60
|
+
for (const chunk of chunks) {
|
|
61
|
+
try {
|
|
62
|
+
const sent = await socket.sendText(jid, chunk);
|
|
63
|
+
recordSentId(sent?.key);
|
|
64
|
+
if (!firstKey && sent?.key)
|
|
65
|
+
firstKey = sent.key;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
logger?.warn?.('whatsapp send failed', { err: String(err) });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return firstKey;
|
|
72
|
+
};
|
|
73
|
+
const pump = new FramePump({
|
|
74
|
+
editFrameMs,
|
|
75
|
+
frame: (final) => {
|
|
76
|
+
const snapshot = renderer.snapshot();
|
|
77
|
+
if (final || snapshot.length <= WHATSAPP_MAX_MESSAGE_CHARS)
|
|
78
|
+
return snapshot;
|
|
79
|
+
// Streaming frame: hold to one editable message; overflow waits for the
|
|
80
|
+
// final flush, whose full text differs from this so a last flush is
|
|
81
|
+
// guaranteed to deliver the tails.
|
|
82
|
+
return `${snapshot.slice(0, WHATSAPP_MAX_MESSAGE_CHARS - 1)}…`;
|
|
83
|
+
},
|
|
84
|
+
emptyFinalText: '(no output)',
|
|
85
|
+
sink: {
|
|
86
|
+
send: async (t) => sendChunks(splitWhatsAppText(t)),
|
|
87
|
+
edit: async (key, t, final) => {
|
|
88
|
+
const chunks = splitWhatsAppText(t);
|
|
89
|
+
try {
|
|
90
|
+
await socket.editText(jid, key, chunks[0]);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
logger?.warn?.('whatsapp edit failed', { err: String(err), final });
|
|
94
|
+
if (final) {
|
|
95
|
+
// Never lose the final reply to a failed edit — resend it whole.
|
|
96
|
+
await sendChunks(chunks);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (final && chunks.length > 1)
|
|
102
|
+
await sendChunks(chunks.slice(1));
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const unsubscribe = subscribeTurn(session, turnId, (event) => {
|
|
107
|
+
if (renderer.accept(event))
|
|
108
|
+
pump.scheduleEdit();
|
|
109
|
+
});
|
|
110
|
+
try {
|
|
111
|
+
await driveTurn(session, {
|
|
112
|
+
turnId,
|
|
113
|
+
prompt: text,
|
|
114
|
+
...(model ? { model } : {}),
|
|
115
|
+
signal: controller.signal,
|
|
116
|
+
});
|
|
117
|
+
await pump.flush(true);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
logger?.warn?.('whatsapp turn failed', {
|
|
121
|
+
err: err instanceof Error ? err.message : String(err),
|
|
122
|
+
});
|
|
123
|
+
await sendChunks([`Turn failed: ${err instanceof Error ? err.message : String(err)}`]);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
unsubscribe();
|
|
127
|
+
pump.dispose();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=turn-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-runner.js","sourceRoot":"","sources":["../../src/channel/turn-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ClientSession as Session } from '@moxxy/sdk';
|
|
2
|
+
import type { WaInboundMessage, WhatsAppSocket } from '../socket.js';
|
|
3
|
+
export interface VoiceHandlerDeps {
|
|
4
|
+
readonly session: Session;
|
|
5
|
+
readonly socket: WhatsAppSocket;
|
|
6
|
+
/** Reply into the originating chat (records sent ids for echo protection). */
|
|
7
|
+
readonly reply: (jid: string, text: string) => Promise<void>;
|
|
8
|
+
readonly logger?: {
|
|
9
|
+
warn?(msg: string, meta?: Record<string, unknown>): void;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface VoiceHandlerInput {
|
|
13
|
+
readonly jid: string;
|
|
14
|
+
readonly mimeType: string;
|
|
15
|
+
/** The raw upsert message — Baileys needs it to decrypt/download the media. */
|
|
16
|
+
readonly raw: WaInboundMessage;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Handle an inbound voice note / audio message that already passed the gate
|
|
20
|
+
* (pairing + allow-list + declared-size cap): require an active Transcriber
|
|
21
|
+
* (guidance reply when none — mirror of the Telegram voice handler), download
|
|
22
|
+
* via Baileys' media pipeline, re-check the size cap on the REAL bytes, then
|
|
23
|
+
* transcribe. Returns the transcript for the caller to run as a normal user
|
|
24
|
+
* turn, or null when the message was fully handled (guidance/error reply sent).
|
|
25
|
+
*/
|
|
26
|
+
export declare function transcribeVoiceMessage(deps: VoiceHandlerDeps, input: VoiceHandlerInput): Promise<string | null>;
|
|
27
|
+
//# sourceMappingURL=voice-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-handler.d.ts","sourceRoot":"","sources":["../../src/channel/voice-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;CAChF;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoDxB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { MAX_AUDIO_BYTES } from '../message-gate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handle an inbound voice note / audio message that already passed the gate
|
|
4
|
+
* (pairing + allow-list + declared-size cap): require an active Transcriber
|
|
5
|
+
* (guidance reply when none — mirror of the Telegram voice handler), download
|
|
6
|
+
* via Baileys' media pipeline, re-check the size cap on the REAL bytes, then
|
|
7
|
+
* transcribe. Returns the transcript for the caller to run as a normal user
|
|
8
|
+
* turn, or null when the message was fully handled (guidance/error reply sent).
|
|
9
|
+
*/
|
|
10
|
+
export async function transcribeVoiceMessage(deps, input) {
|
|
11
|
+
const { session, socket, reply, logger } = deps;
|
|
12
|
+
const { jid, mimeType, raw } = input;
|
|
13
|
+
const transcriber = session.transcribers.tryGetActive();
|
|
14
|
+
if (!transcriber) {
|
|
15
|
+
await reply(jid, 'Heard a voice note, but no speech-to-text backend is configured. Install ' +
|
|
16
|
+
'@moxxy/plugin-stt-whisper and run `moxxy login openai` (or set OPENAI_API_KEY) ' +
|
|
17
|
+
'to enable voice input.');
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
let bytes;
|
|
21
|
+
try {
|
|
22
|
+
bytes = await socket.downloadMedia(raw);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
logger?.warn?.('whatsapp voice download failed', {
|
|
26
|
+
err: err instanceof Error ? err.message : String(err),
|
|
27
|
+
});
|
|
28
|
+
await reply(jid, 'Could not download that voice note.');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (bytes.byteLength > MAX_AUDIO_BYTES) {
|
|
32
|
+
await reply(jid, `That audio is too large to transcribe (limit ${MAX_AUDIO_BYTES / (1024 * 1024)}MB).`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
let transcript;
|
|
36
|
+
try {
|
|
37
|
+
transcript = (await transcriber.transcribe(bytes, { mimeType })).text.trim();
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logger?.warn?.('whatsapp voice transcription failed', {
|
|
41
|
+
err: err instanceof Error ? err.message : String(err),
|
|
42
|
+
});
|
|
43
|
+
await reply(jid, `Transcription failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (!transcript) {
|
|
47
|
+
await reply(jid, 'Could not transcribe the voice note (got empty text).');
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Echo what was heard so the user can spot misrecognitions before the agent
|
|
51
|
+
// acts on it (same contract as the Telegram voice handler).
|
|
52
|
+
await reply(jid, `heard: ${transcript}`);
|
|
53
|
+
return transcript;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=voice-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-handler.js","sourceRoot":"","sources":["../../src/channel/voice-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAkBrD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAsB,EACtB,KAAwB;IAExB,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChD,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IAErC,MAAM,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC;IACxD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,KAAK,CACT,GAAG,EACH,2EAA2E;YACzE,iFAAiF;YACjF,wBAAwB,CAC3B,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,KAAiB,CAAC;IACtB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,IAAI,EAAE,CAAC,gCAAgC,EAAE;YAC/C,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACtD,CAAC,CAAC;QACH,MAAM,KAAK,CAAC,GAAG,EAAE,qCAAqC,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,GAAG,eAAe,EAAE,CAAC;QACvC,MAAM,KAAK,CACT,GAAG,EACH,gDAAgD,eAAe,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CACtF,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACH,UAAU,GAAG,CAAC,MAAM,WAAW,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC/E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,IAAI,EAAE,CAAC,qCAAqC,EAAE;YACpD,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACtD,CAAC,CAAC;QACH,MAAM,KAAK,CAAC,GAAG,EAAE,yBAAyB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9F,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC,GAAG,EAAE,uDAAuD,CAAC,CAAC;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4EAA4E;IAC5E,4DAA4D;IAC5D,MAAM,KAAK,CAAC,GAAG,EAAE,UAAU,UAAU,EAAE,CAAC,CAAC;IACzC,OAAO,UAAU,CAAC;AACpB,CAAC"}
|