@japofc/baileys 2.1.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/NOTICE.md +111 -0
- package/README.md +1254 -0
- package/WAProto/index.d.ts +9 -0
- package/WAProto/index.js +142276 -0
- package/engine-requirements.js +13 -0
- package/lib/Builders/A2UI.d.ts +242 -0
- package/lib/Builders/A2UI.js +308 -0
- package/lib/Builders/AIRich.d.ts +105 -0
- package/lib/Builders/AIRich.js +2286 -0
- package/lib/Builders/Button.d.ts +103 -0
- package/lib/Builders/Button.js +883 -0
- package/lib/Builders/ButtonV2.d.ts +28 -0
- package/lib/Builders/ButtonV2.js +144 -0
- package/lib/Builders/ButtonV3.d.ts +25 -0
- package/lib/Builders/ButtonV3.js +184 -0
- package/lib/Builders/Carousel.d.ts +22 -0
- package/lib/Builders/Carousel.js +111 -0
- package/lib/Builders/JapBaileys.d.ts +34 -0
- package/lib/Builders/JapBaileys.js +108 -0
- package/lib/Builders/Poll.d.ts +25 -0
- package/lib/Builders/Poll.js +128 -0
- package/lib/Builders/index.d.ts +17 -0
- package/lib/Builders/index.js +11 -0
- package/lib/Builders/shared.d.ts +67 -0
- package/lib/Builders/shared.js +652 -0
- package/lib/Defaults/index.d.ts +154 -0
- package/lib/Defaults/index.js +178 -0
- package/lib/Framework/Bot.d.ts +61 -0
- package/lib/Framework/Bot.js +248 -0
- package/lib/Framework/Context.d.ts +21 -0
- package/lib/Framework/Context.js +89 -0
- package/lib/Framework/MediaManager.d.ts +17 -0
- package/lib/Framework/MediaManager.js +151 -0
- package/lib/Framework/SessionManager.d.ts +13 -0
- package/lib/Framework/SessionManager.js +33 -0
- package/lib/Framework/StatsManager.d.ts +26 -0
- package/lib/Framework/StatsManager.js +119 -0
- package/lib/Framework/Store/SQLiteStore.d.ts +11 -0
- package/lib/Framework/Store/SQLiteStore.js +73 -0
- package/lib/Framework/index.d.ts +9 -0
- package/lib/Framework/index.js +10 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
- package/lib/Signal/Group/ciphertext-message.js +11 -0
- package/lib/Signal/Group/group-session-builder.d.ts +7 -0
- package/lib/Signal/Group/group-session-builder.js +29 -0
- package/lib/Signal/Group/group_cipher.d.ts +10 -0
- package/lib/Signal/Group/group_cipher.js +81 -0
- package/lib/Signal/Group/index.d.ts +11 -0
- package/lib/Signal/Group/index.js +11 -0
- package/lib/Signal/Group/keyhelper.d.ts +6 -0
- package/lib/Signal/Group/keyhelper.js +17 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
- package/lib/Signal/Group/sender-chain-key.js +25 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
- package/lib/Signal/Group/sender-key-message.d.ts +18 -0
- package/lib/Signal/Group/sender-key-message.js +65 -0
- package/lib/Signal/Group/sender-key-name.d.ts +11 -0
- package/lib/Signal/Group/sender-key-name.js +47 -0
- package/lib/Signal/Group/sender-key-record.d.ts +12 -0
- package/lib/Signal/Group/sender-key-record.js +40 -0
- package/lib/Signal/Group/sender-key-state.d.ts +16 -0
- package/lib/Signal/Group/sender-key-state.js +83 -0
- package/lib/Signal/Group/sender-message-key.d.ts +11 -0
- package/lib/Signal/Group/sender-message-key.js +25 -0
- package/lib/Signal/libsignal.d.ts +54 -0
- package/lib/Signal/libsignal.js +430 -0
- package/lib/Signal/lid-mapping.d.ts +20 -0
- package/lib/Signal/lid-mapping.js +276 -0
- package/lib/Socket/Client/index.d.ts +2 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.d.ts +5 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.d.ts +12 -0
- package/lib/Socket/Client/websocket.js +53 -0
- package/lib/Socket/business.d.ts +395 -0
- package/lib/Socket/business.js +379 -0
- package/lib/Socket/chats.d.ts +136 -0
- package/lib/Socket/chats.js +1327 -0
- package/lib/Socket/communities.d.ts +562 -0
- package/lib/Socket/communities.js +439 -0
- package/lib/Socket/dugong.d.ts +117 -0
- package/lib/Socket/dugong.js +798 -0
- package/lib/Socket/groups.d.ts +285 -0
- package/lib/Socket/groups.js +354 -0
- package/lib/Socket/index.d.ts +585 -0
- package/lib/Socket/index.js +52 -0
- package/lib/Socket/messages-recv.d.ts +333 -0
- package/lib/Socket/messages-recv.js +1781 -0
- package/lib/Socket/messages-send.d.ts +353 -0
- package/lib/Socket/messages-send.js +1524 -0
- package/lib/Socket/mex.d.ts +1 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.d.ts +286 -0
- package/lib/Socket/newsletter.js +364 -0
- package/lib/Socket/socket.d.ts +60 -0
- package/lib/Socket/socket.js +1012 -0
- package/lib/Socket/username.d.ts +82 -0
- package/lib/Socket/username.js +159 -0
- package/lib/Store/index.d.ts +3 -0
- package/lib/Store/index.js +3 -0
- package/lib/Store/make-in-memory-store.d.ts +62 -0
- package/lib/Store/make-in-memory-store.js +428 -0
- package/lib/Store/make-ordered-dictionary.d.ts +12 -0
- package/lib/Store/make-ordered-dictionary.js +78 -0
- package/lib/Store/object-repository.d.ts +10 -0
- package/lib/Store/object-repository.js +23 -0
- package/lib/Types/Auth.d.ts +117 -0
- package/lib/Types/Auth.js +1 -0
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Call.d.ts +15 -0
- package/lib/Types/Call.js +1 -0
- package/lib/Types/Chat.d.ts +124 -0
- package/lib/Types/Chat.js +7 -0
- package/lib/Types/Contact.d.ts +26 -0
- package/lib/Types/Contact.js +1 -0
- package/lib/Types/Events.d.ts +256 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.d.ts +71 -0
- package/lib/Types/GroupMetadata.js +1 -0
- package/lib/Types/Label.d.ts +5 -0
- package/lib/Types/Label.js +24 -0
- package/lib/Types/LabelAssociation.d.ts +6 -0
- package/lib/Types/LabelAssociation.js +6 -0
- package/lib/Types/Message.d.ts +62 -0
- package/lib/Types/Message.js +17 -0
- package/lib/Types/Mex.d.ts +2 -0
- package/lib/Types/Mex.js +38 -0
- package/lib/Types/Product.d.ts +79 -0
- package/lib/Types/Product.js +1 -0
- package/lib/Types/RichType.d.ts +2 -0
- package/lib/Types/RichType.js +22 -0
- package/lib/Types/Signal.d.ts +87 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.d.ts +138 -0
- package/lib/Types/Socket.js +1 -0
- package/lib/Types/State.d.ts +8 -0
- package/lib/Types/State.js +55 -0
- package/lib/Types/USync.d.ts +26 -0
- package/lib/Types/index.d.ts +68 -0
- package/lib/Types/index.js +27 -0
- package/lib/Utils/MessageBuilder.d.ts +2 -0
- package/lib/Utils/MessageBuilder.js +2 -0
- package/lib/Utils/PersistentStore.d.ts +60 -0
- package/lib/Utils/PersistentStore.js +592 -0
- package/lib/Utils/anti-delete.d.ts +74 -0
- package/lib/Utils/anti-delete.js +221 -0
- package/lib/Utils/auth-secure.d.ts +86 -0
- package/lib/Utils/auth-secure.js +764 -0
- package/lib/Utils/auth-utils.d.ts +57 -0
- package/lib/Utils/auth-utils.js +306 -0
- package/lib/Utils/auto-reply.d.ts +47 -0
- package/lib/Utils/auto-reply.js +157 -0
- package/lib/Utils/baileys-event-stream.d.ts +8 -0
- package/lib/Utils/baileys-event-stream.js +62 -0
- package/lib/Utils/browser-utils.d.ts +8 -0
- package/lib/Utils/browser-utils.js +27 -0
- package/lib/Utils/business.d.ts +49 -0
- package/lib/Utils/business.js +239 -0
- package/lib/Utils/button-helper-utils.d.ts +53 -0
- package/lib/Utils/button-helper-utils.js +312 -0
- package/lib/Utils/button-sender.d.ts +54 -0
- package/lib/Utils/button-sender.js +822 -0
- package/lib/Utils/chat-control.d.ts +159 -0
- package/lib/Utils/chat-control.js +230 -0
- package/lib/Utils/chat-history-helpers.d.ts +21 -0
- package/lib/Utils/chat-history-helpers.js +70 -0
- package/lib/Utils/chat-utils.d.ts +407 -0
- package/lib/Utils/chat-utils.js +885 -0
- package/lib/Utils/companion-reg-client-utils.d.ts +4 -0
- package/lib/Utils/companion-reg-client-utils.js +39 -0
- package/lib/Utils/crypto.d.ts +41 -0
- package/lib/Utils/crypto.js +147 -0
- package/lib/Utils/decode-wa-message.d.ts +83 -0
- package/lib/Utils/decode-wa-message.js +316 -0
- package/lib/Utils/event-buffer.d.ts +12 -0
- package/lib/Utils/event-buffer.js +635 -0
- package/lib/Utils/generics.d.ts +69 -0
- package/lib/Utils/generics.js +438 -0
- package/lib/Utils/history-share.d.ts +11 -0
- package/lib/Utils/history-share.js +70 -0
- package/lib/Utils/history.d.ts +90 -0
- package/lib/Utils/history.js +146 -0
- package/lib/Utils/humanizer.d.ts +12 -0
- package/lib/Utils/humanizer.js +73 -0
- package/lib/Utils/identity-change-handler.d.ts +13 -0
- package/lib/Utils/identity-change-handler.js +49 -0
- package/lib/Utils/index.d.ts +57 -0
- package/lib/Utils/index.js +56 -0
- package/lib/Utils/jid-tools.d.ts +6 -0
- package/lib/Utils/jid-tools.js +77 -0
- package/lib/Utils/link-preview.d.ts +12 -0
- package/lib/Utils/link-preview.js +84 -0
- package/lib/Utils/logger.d.ts +4 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.d.ts +12 -0
- package/lib/Utils/lt-hash.js +26 -0
- package/lib/Utils/make-mutex.d.ts +6 -0
- package/lib/Utils/make-mutex.js +32 -0
- package/lib/Utils/media-messages.d.ts +18 -0
- package/lib/Utils/media-messages.js +70 -0
- package/lib/Utils/media-set.d.ts +13 -0
- package/lib/Utils/media-set.js +171 -0
- package/lib/Utils/message-kind.d.ts +32 -0
- package/lib/Utils/message-kind.js +136 -0
- package/lib/Utils/message-retry-manager.d.ts +79 -0
- package/lib/Utils/message-retry-manager.js +264 -0
- package/lib/Utils/message-search.d.ts +44 -0
- package/lib/Utils/message-search.js +188 -0
- package/lib/Utils/messages-media.d.ts +135 -0
- package/lib/Utils/messages-media.js +868 -0
- package/lib/Utils/messages.d.ts +44 -0
- package/lib/Utils/messages.js +2326 -0
- package/lib/Utils/meta-ai.d.ts +13 -0
- package/lib/Utils/meta-ai.js +74 -0
- package/lib/Utils/mini-app.d.ts +35 -0
- package/lib/Utils/mini-app.js +213 -0
- package/lib/Utils/noise-handler.d.ts +20 -0
- package/lib/Utils/noise-handler.js +200 -0
- package/lib/Utils/offline-node-processor.d.ts +9 -0
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/past-participants.d.ts +14 -0
- package/lib/Utils/past-participants.js +40 -0
- package/lib/Utils/pre-key-manager.d.ts +25 -0
- package/lib/Utils/pre-key-manager.js +105 -0
- package/lib/Utils/process-message.d.ts +82 -0
- package/lib/Utils/process-message.js +808 -0
- package/lib/Utils/qr-render.d.ts +59 -0
- package/lib/Utils/qr-render.js +179 -0
- package/lib/Utils/qrcodegen.d.ts +79 -0
- package/lib/Utils/qrcodegen.js +848 -0
- package/lib/Utils/reporting-utils.d.ts +12 -0
- package/lib/Utils/reporting-utils.js +257 -0
- package/lib/Utils/rich-message-utils.d.ts +48 -0
- package/lib/Utils/rich-message-utils.js +444 -0
- package/lib/Utils/router.d.ts +37 -0
- package/lib/Utils/router.js +165 -0
- package/lib/Utils/scheduled-posts.d.ts +30 -0
- package/lib/Utils/scheduled-posts.js +110 -0
- package/lib/Utils/scheduling.d.ts +42 -0
- package/lib/Utils/scheduling.js +139 -0
- package/lib/Utils/signal.d.ts +82 -0
- package/lib/Utils/signal.js +200 -0
- package/lib/Utils/stanza-ack.d.ts +16 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/Utils/status.d.ts +50 -0
- package/lib/Utils/status.js +107 -0
- package/lib/Utils/stickerpack.d.ts +51 -0
- package/lib/Utils/stickerpack.js +274 -0
- package/lib/Utils/sync-action-utils.d.ts +2 -0
- package/lib/Utils/sync-action-utils.js +48 -0
- package/lib/Utils/tag.d.ts +12 -0
- package/lib/Utils/tag.js +44 -0
- package/lib/Utils/tc-token-utils.d.ts +29 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/templates.d.ts +76 -0
- package/lib/Utils/templates.js +150 -0
- package/lib/Utils/transcribe.d.ts +12 -0
- package/lib/Utils/transcribe.js +107 -0
- package/lib/Utils/use-cache-manager-auth-state.d.ts +13 -0
- package/lib/Utils/use-cache-manager-auth-state.js +81 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +10 -0
- package/lib/Utils/use-multi-file-auth-state.js +166 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +10 -0
- package/lib/Utils/use-single-file-auth-state.js +113 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
- package/lib/Utils/use-sqlite-auth-state.js +169 -0
- package/lib/Utils/validate-connection.d.ts +42 -0
- package/lib/Utils/validate-connection.js +198 -0
- package/lib/Utils/vcard.d.ts +58 -0
- package/lib/Utils/vcard.js +104 -0
- package/lib/Utils/voice-note.d.ts +15 -0
- package/lib/Utils/voice-note.js +63 -0
- package/lib/VoIP/audio-feeder.d.ts +18 -0
- package/lib/VoIP/audio-feeder.js +160 -0
- package/lib/VoIP/call-recorder.d.ts +13 -0
- package/lib/VoIP/call-recorder.js +73 -0
- package/lib/VoIP/index.d.ts +86 -0
- package/lib/VoIP/index.js +611 -0
- package/lib/VoIP/relay-transport.d.ts +43 -0
- package/lib/VoIP/relay-transport.js +563 -0
- package/lib/VoIP/signaling.d.ts +26 -0
- package/lib/VoIP/signaling.js +662 -0
- package/lib/VoIP/types.d.ts +69 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +120 -0
- package/lib/VoIP/wasm-engine.js +1358 -0
- package/lib/VoIP/worker-bootstrap.d.ts +4 -0
- package/lib/VoIP/worker-bootstrap.js +1046 -0
- package/lib/WABinary/constants.d.ts +61 -0
- package/lib/WABinary/constants.js +1466 -0
- package/lib/WABinary/decode.d.ts +9 -0
- package/lib/WABinary/decode.js +261 -0
- package/lib/WABinary/encode.d.ts +2 -0
- package/lib/WABinary/encode.js +219 -0
- package/lib/WABinary/generic-utils.d.ts +74 -0
- package/lib/WABinary/generic-utils.js +276 -0
- package/lib/WABinary/index.d.ts +5 -0
- package/lib/WABinary/index.js +5 -0
- package/lib/WABinary/jid-utils.d.ts +28 -0
- package/lib/WABinary/jid-utils.js +95 -0
- package/lib/WABinary/types.d.ts +19 -0
- package/lib/WABinary/types.js +1 -0
- package/lib/WAM/BinaryInfo.d.ts +7 -0
- package/lib/WAM/BinaryInfo.js +9 -0
- package/lib/WAM/constants.d.ts +34926 -0
- package/lib/WAM/constants.js +22852 -0
- package/lib/WAM/encode.d.ts +1 -0
- package/lib/WAM/encode.js +149 -0
- package/lib/WAM/index.d.ts +3 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +36 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +51 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +29 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +62 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +9 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +38 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +14 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
- package/lib/WAUSync/Protocols/index.d.ts +7 -0
- package/lib/WAUSync/Protocols/index.js +7 -0
- package/lib/WAUSync/USyncQuery.d.ts +20 -0
- package/lib/WAUSync/USyncQuery.js +121 -0
- package/lib/WAUSync/USyncUser.d.ts +16 -0
- package/lib/WAUSync/USyncUser.js +30 -0
- package/lib/WAUSync/index.d.ts +3 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/assets/wasm/loader.d.ts +5 -0
- package/lib/assets/wasm/loader.js +5 -0
- package/lib/assets/wasm/whatsapp.wasm +0 -0
- package/lib/assets/wasm/worker-modules.d.ts +5 -0
- package/lib/assets/wasm/worker-modules.js +273 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +23 -0
- package/package.json +180 -0
- package/postinstall-banner.js +82 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Boom } from '@hapi/boom';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { proto } from '../../WAProto/index.js';
|
|
4
|
+
import { KEY_BUNDLE_TYPE, WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX, COMPANION_DEVICE_VERSION } from '../Defaults/index.js';
|
|
5
|
+
import { getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
6
|
+
import { Curve, hmacSign } from './crypto.js';
|
|
7
|
+
import { encodeBigEndian } from './generics.js';
|
|
8
|
+
import { createSignalIdentity } from './signal.js';
|
|
9
|
+
const getUserAgent = (config) => {
|
|
10
|
+
return {
|
|
11
|
+
appVersion: {
|
|
12
|
+
primary: config.version[0],
|
|
13
|
+
secondary: config.version[1],
|
|
14
|
+
tertiary: config.version[2]
|
|
15
|
+
},
|
|
16
|
+
platform: proto.ClientPayload.UserAgent.Platform.WEB,
|
|
17
|
+
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
|
18
|
+
osVersion: '0.1',
|
|
19
|
+
device: 'Desktop',
|
|
20
|
+
osBuildNumber: '0.1',
|
|
21
|
+
localeLanguageIso6391: 'en',
|
|
22
|
+
mnc: '000',
|
|
23
|
+
mcc: '000',
|
|
24
|
+
localeCountryIso31661Alpha2: config.countryCode
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const PLATFORM_MAP = {
|
|
28
|
+
'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
|
|
29
|
+
Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
|
|
30
|
+
};
|
|
31
|
+
const getWebInfo = (config) => {
|
|
32
|
+
let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER;
|
|
33
|
+
if (config.syncFullHistory &&
|
|
34
|
+
PLATFORM_MAP[config.browser[0]] &&
|
|
35
|
+
config.browser[1] === 'Desktop') {
|
|
36
|
+
webSubPlatform = PLATFORM_MAP[config.browser[0]];
|
|
37
|
+
}
|
|
38
|
+
return { webSubPlatform };
|
|
39
|
+
};
|
|
40
|
+
const getClientPayload = (config) => {
|
|
41
|
+
const payload = {
|
|
42
|
+
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
|
43
|
+
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
|
44
|
+
userAgent: getUserAgent(config)
|
|
45
|
+
};
|
|
46
|
+
payload.webInfo = getWebInfo(config);
|
|
47
|
+
if (config.pushName) {
|
|
48
|
+
payload.pushName = config.pushName;
|
|
49
|
+
}
|
|
50
|
+
return payload;
|
|
51
|
+
};
|
|
52
|
+
export const generateLoginNode = (userJid, config) => {
|
|
53
|
+
const { user, device } = jidDecode(userJid);
|
|
54
|
+
const payload = {
|
|
55
|
+
...getClientPayload(config),
|
|
56
|
+
passive: true,
|
|
57
|
+
pull: true,
|
|
58
|
+
username: +user,
|
|
59
|
+
device: device,
|
|
60
|
+
// JAP@Fix (bug 49): was hardcoded false — now reads from config.lidDbMigrated (default false until investigated)
|
|
61
|
+
lidDbMigrated: config.lidDbMigrated ?? false
|
|
62
|
+
};
|
|
63
|
+
return proto.ClientPayload.fromObject(payload);
|
|
64
|
+
};
|
|
65
|
+
const getPlatformType = (platform) => {
|
|
66
|
+
const platformType = platform.toUpperCase();
|
|
67
|
+
return (proto.DeviceProps.PlatformType[platformType] ||
|
|
68
|
+
proto.DeviceProps.PlatformType.CHROME);
|
|
69
|
+
};
|
|
70
|
+
export const generateRegistrationNode = ({ registrationId, signedPreKey, signedIdentityKey }, config) => {
|
|
71
|
+
// the app version needs to be md5 hashed
|
|
72
|
+
// and passed in
|
|
73
|
+
const appVersionBuf = createHash('md5')
|
|
74
|
+
.update(config.version.join('.')) // join as string
|
|
75
|
+
.digest();
|
|
76
|
+
const companion = {
|
|
77
|
+
os: config.browser[0],
|
|
78
|
+
platformType: getPlatformType(config.browser[1]),
|
|
79
|
+
requireFullSync: config.syncFullHistory,
|
|
80
|
+
historySyncConfig: {
|
|
81
|
+
storageQuotaMb: 10240,
|
|
82
|
+
inlineInitialPayloadInE2EeMsg: true,
|
|
83
|
+
recentSyncDaysLimit: undefined,
|
|
84
|
+
supportCallLogHistory: false,
|
|
85
|
+
supportBotUserAgentChatHistory: true,
|
|
86
|
+
supportCagReactionsAndPolls: true,
|
|
87
|
+
supportBizHostedMsg: true,
|
|
88
|
+
supportRecentSyncChunkMessageCountTuning: true,
|
|
89
|
+
supportHostedGroupMsg: true,
|
|
90
|
+
supportFbidBotChatHistory: true,
|
|
91
|
+
supportAddOnHistorySyncMigration: undefined,
|
|
92
|
+
supportMessageAssociation: true,
|
|
93
|
+
supportGroupHistory: false,
|
|
94
|
+
onDemandReady: undefined,
|
|
95
|
+
supportGuestChat: undefined
|
|
96
|
+
},
|
|
97
|
+
version: COMPANION_DEVICE_VERSION
|
|
98
|
+
};
|
|
99
|
+
const companionProto = proto.DeviceProps.encode(companion).finish();
|
|
100
|
+
const registerPayload = {
|
|
101
|
+
...getClientPayload(config),
|
|
102
|
+
passive: false,
|
|
103
|
+
pull: false,
|
|
104
|
+
devicePairingData: {
|
|
105
|
+
buildHash: appVersionBuf,
|
|
106
|
+
deviceProps: companionProto,
|
|
107
|
+
eRegid: encodeBigEndian(registrationId),
|
|
108
|
+
eKeytype: KEY_BUNDLE_TYPE,
|
|
109
|
+
eIdent: signedIdentityKey.public,
|
|
110
|
+
eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
|
|
111
|
+
eSkeyVal: signedPreKey.keyPair.public,
|
|
112
|
+
eSkeySig: signedPreKey.signature
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
return proto.ClientPayload.fromObject(registerPayload);
|
|
116
|
+
};
|
|
117
|
+
export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => {
|
|
118
|
+
const msgId = stanza.attrs.id;
|
|
119
|
+
const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success');
|
|
120
|
+
const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity');
|
|
121
|
+
const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform');
|
|
122
|
+
const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device');
|
|
123
|
+
const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz');
|
|
124
|
+
if (!deviceIdentityNode || !deviceNode) {
|
|
125
|
+
throw new Boom('Missing device-identity or device in pair success node', { data: stanza });
|
|
126
|
+
}
|
|
127
|
+
const bizName = businessNode?.attrs.name;
|
|
128
|
+
const jid = deviceNode.attrs.jid;
|
|
129
|
+
const lid = deviceNode.attrs.lid;
|
|
130
|
+
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content);
|
|
131
|
+
let hmacPrefix = Buffer.from([]);
|
|
132
|
+
if (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED) {
|
|
133
|
+
hmacPrefix = WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX;
|
|
134
|
+
}
|
|
135
|
+
const advSign = hmacSign(Buffer.concat([hmacPrefix, details]), Buffer.from(advSecretKey, 'base64'));
|
|
136
|
+
if (Buffer.compare(hmac, advSign) !== 0) {
|
|
137
|
+
throw new Boom('Invalid account signature');
|
|
138
|
+
}
|
|
139
|
+
const account = proto.ADVSignedDeviceIdentity.decode(details);
|
|
140
|
+
const { accountSignatureKey, accountSignature, details: deviceDetails } = account;
|
|
141
|
+
const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails);
|
|
142
|
+
const accountSignaturePrefix = deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
|
|
143
|
+
? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
|
144
|
+
: WA_ADV_ACCOUNT_SIG_PREFIX;
|
|
145
|
+
const accountMsg = Buffer.concat([accountSignaturePrefix, deviceDetails, signedIdentityKey.public]);
|
|
146
|
+
if (!Curve.verify(accountSignatureKey, accountMsg, accountSignature)) {
|
|
147
|
+
throw new Boom('Failed to verify account signature');
|
|
148
|
+
}
|
|
149
|
+
const deviceMsg = Buffer.concat([
|
|
150
|
+
WA_ADV_DEVICE_SIG_PREFIX,
|
|
151
|
+
deviceDetails,
|
|
152
|
+
signedIdentityKey.public,
|
|
153
|
+
accountSignatureKey
|
|
154
|
+
]);
|
|
155
|
+
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg);
|
|
156
|
+
const identity = createSignalIdentity(lid, accountSignatureKey);
|
|
157
|
+
const accountEnc = encodeSignedDeviceIdentity(account, false);
|
|
158
|
+
const reply = {
|
|
159
|
+
tag: 'iq',
|
|
160
|
+
attrs: {
|
|
161
|
+
to: S_WHATSAPP_NET,
|
|
162
|
+
type: 'result',
|
|
163
|
+
id: msgId
|
|
164
|
+
},
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
tag: 'pair-device-sign',
|
|
168
|
+
attrs: {},
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
tag: 'device-identity',
|
|
172
|
+
attrs: { 'key-index': deviceIdentity.keyIndex.toString() },
|
|
173
|
+
content: accountEnc
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
};
|
|
179
|
+
const authUpdate = {
|
|
180
|
+
account,
|
|
181
|
+
me: { id: jid, name: bizName, lid },
|
|
182
|
+
signalIdentities: [...(signalIdentities || []), identity],
|
|
183
|
+
platform: platformNode?.attrs.name
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
creds: authUpdate,
|
|
187
|
+
reply
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
|
|
191
|
+
account = { ...account };
|
|
192
|
+
// set to null if we are not to include the signature key
|
|
193
|
+
// or if we are including the signature key but it is empty
|
|
194
|
+
if (!includeSignatureKey || !account.accountSignatureKey?.length) {
|
|
195
|
+
account.accountSignatureKey = null;
|
|
196
|
+
}
|
|
197
|
+
return proto.ADVSignedDeviceIdentity.encode(account).finish();
|
|
198
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface ContactPhone {
|
|
2
|
+
number: string;
|
|
3
|
+
type?: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ContactEmail {
|
|
7
|
+
email: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ContactUrl {
|
|
11
|
+
url: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ContactAddress {
|
|
15
|
+
street?: string;
|
|
16
|
+
city?: string;
|
|
17
|
+
state?: string;
|
|
18
|
+
postalCode?: string;
|
|
19
|
+
country?: string;
|
|
20
|
+
type?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ContactData {
|
|
23
|
+
fullName: string;
|
|
24
|
+
displayName?: string;
|
|
25
|
+
organization?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
phones?: ContactPhone[];
|
|
28
|
+
emails?: ContactEmail[];
|
|
29
|
+
urls?: ContactUrl[];
|
|
30
|
+
addresses?: ContactAddress[];
|
|
31
|
+
birthday?: string;
|
|
32
|
+
note?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const escapeVCard: (s: string) => string;
|
|
35
|
+
export declare const formatPhone: (p: string) => string;
|
|
36
|
+
export declare const generateVCard: (c: ContactData) => string;
|
|
37
|
+
export declare const generateVCards: (contacts: ContactData[]) => string;
|
|
38
|
+
export declare const parseVCard: (vcard: string) => Partial<ContactData>;
|
|
39
|
+
export declare const createContactCard: (contact: ContactData) => {
|
|
40
|
+
contacts: {
|
|
41
|
+
displayName: string;
|
|
42
|
+
contacts: {
|
|
43
|
+
vcard: string;
|
|
44
|
+
}[];
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export declare const createContactCards: (contacts: ContactData[]) => {
|
|
48
|
+
contacts: {
|
|
49
|
+
displayName: string;
|
|
50
|
+
contacts: {
|
|
51
|
+
vcard: string;
|
|
52
|
+
}[];
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export declare const quickContact: (name: string, phone: string, options?: {
|
|
56
|
+
organization?: string;
|
|
57
|
+
email?: string;
|
|
58
|
+
}) => ContactData;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// JAP@Add --- part of the @japofc/baileys utils (vcard.ts) (type-only
|
|
2
|
+
// annotations dropped; behavior unchanged).
|
|
3
|
+
export const escapeVCard = (s) => s.replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n');
|
|
4
|
+
// JAP@Fix 30-08-26 --- the old inline unescape used for FN/ORG/TITLE,
|
|
5
|
+
// `value.replace(/\\([;,n\\])/g, '$1')`, mishandled the `\n` case: it matched
|
|
6
|
+
// the backslash + literal "n" produced by escapeVCard's `\n` escape, then
|
|
7
|
+
// substituted back just the captured "n" character instead of a real newline
|
|
8
|
+
// (`$1` is literally the char "n", not the escape sequence). So a name/org/
|
|
9
|
+
// title containing a real newline round-tripped as "n" inserted in place of
|
|
10
|
+
// the line break instead of the original newline. NOTE already unescaped
|
|
11
|
+
// `\n` correctly on its own, separately. This single helper now reverses
|
|
12
|
+
// every escape escapeVCard() produces (backslash, semicolon, comma, newline)
|
|
13
|
+
// the same correct way, and is reused for all four fields below.
|
|
14
|
+
export const unescapeVCard = (s) => s.replace(/\\(.)/g, (_, ch) => (ch === 'n' ? '\n' : ch));
|
|
15
|
+
export const formatPhone = (p) => p.replace(/[^\d+]/g, '');
|
|
16
|
+
/** Build a VCARD 3.0 string from structured contact data. */
|
|
17
|
+
export const generateVCard = (c) => {
|
|
18
|
+
const lines = ['BEGIN:VCARD', 'VERSION:3.0', `FN:${escapeVCard(c.fullName)}`];
|
|
19
|
+
const parts = c.fullName.split(' ');
|
|
20
|
+
if (parts.length >= 2) {
|
|
21
|
+
const last = parts[parts.length - 1] || '';
|
|
22
|
+
const first = parts.slice(0, -1).join(' ');
|
|
23
|
+
lines.push(`N:${escapeVCard(last)};${escapeVCard(first)};;;`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
lines.push(`N:${escapeVCard(c.fullName)};;;;`);
|
|
27
|
+
}
|
|
28
|
+
if (c.organization)
|
|
29
|
+
lines.push(`ORG:${escapeVCard(c.organization)}`);
|
|
30
|
+
if (c.title)
|
|
31
|
+
lines.push(`TITLE:${escapeVCard(c.title)}`);
|
|
32
|
+
for (const p of c.phones ?? []) {
|
|
33
|
+
const t = p.type || 'CELL';
|
|
34
|
+
const n = formatPhone(p.number);
|
|
35
|
+
lines.push(p.label ? `TEL;type=${t};type=VOICE;X-ABLabel=${escapeVCard(p.label)}:${n}` : `TEL;type=${t};type=VOICE:${n}`);
|
|
36
|
+
}
|
|
37
|
+
for (const e of c.emails ?? [])
|
|
38
|
+
lines.push(`EMAIL;type=${e.type || 'OTHER'}:${e.email}`);
|
|
39
|
+
for (const u of c.urls ?? [])
|
|
40
|
+
lines.push(`URL;type=${u.type || 'OTHER'}:${u.url}`);
|
|
41
|
+
for (const a of c.addresses ?? []) {
|
|
42
|
+
const t = a.type || 'OTHER';
|
|
43
|
+
const parts = ['', '', a.street || '', a.city || '', a.state || '', a.postalCode || '', a.country || ''].map((v) => escapeVCard(v));
|
|
44
|
+
lines.push(`ADR;type=${t}:${parts.join(';')}`);
|
|
45
|
+
}
|
|
46
|
+
if (c.birthday)
|
|
47
|
+
lines.push(`BDAY:${c.birthday}`);
|
|
48
|
+
if (c.note)
|
|
49
|
+
lines.push(`NOTE:${escapeVCard(c.note)}`);
|
|
50
|
+
lines.push('END:VCARD');
|
|
51
|
+
return lines.join('\r\n');
|
|
52
|
+
};
|
|
53
|
+
export const generateVCards = (contacts) => contacts.map(generateVCard).join('\r\n');
|
|
54
|
+
/** Parse a subset of VCARD fields back into structured data. */
|
|
55
|
+
export const parseVCard = (vcard) => {
|
|
56
|
+
const contact = {};
|
|
57
|
+
for (const line of vcard.split(/\r?\n/)) {
|
|
58
|
+
const [key, ...vp] = line.split(':');
|
|
59
|
+
if (!key)
|
|
60
|
+
continue;
|
|
61
|
+
const value = vp.join(':');
|
|
62
|
+
if (key.startsWith('FN'))
|
|
63
|
+
contact.fullName = unescapeVCard(value);
|
|
64
|
+
else if (key.startsWith('ORG'))
|
|
65
|
+
contact.organization = unescapeVCard(value);
|
|
66
|
+
else if (key.startsWith('TITLE'))
|
|
67
|
+
contact.title = unescapeVCard(value);
|
|
68
|
+
else if (key.startsWith('TEL')) {
|
|
69
|
+
contact.phones = contact.phones || [];
|
|
70
|
+
const tm = key.match(/type=(\w+)/i);
|
|
71
|
+
contact.phones.push({ number: value, type: tm?.[1]?.toUpperCase() || 'CELL' });
|
|
72
|
+
}
|
|
73
|
+
else if (key.startsWith('EMAIL')) {
|
|
74
|
+
contact.emails = contact.emails || [];
|
|
75
|
+
const tm = key.match(/type=(\w+)/i);
|
|
76
|
+
contact.emails.push({ email: value, type: tm?.[1]?.toUpperCase() || 'OTHER' });
|
|
77
|
+
}
|
|
78
|
+
else if (key.startsWith('BDAY'))
|
|
79
|
+
contact.birthday = value;
|
|
80
|
+
else if (key.startsWith('NOTE'))
|
|
81
|
+
contact.note = unescapeVCard(value);
|
|
82
|
+
}
|
|
83
|
+
return contact;
|
|
84
|
+
};
|
|
85
|
+
/** Ready-to-send `contacts` message content for a single contact. */
|
|
86
|
+
export const createContactCard = (contact) => ({
|
|
87
|
+
contacts: {
|
|
88
|
+
displayName: contact.displayName || contact.fullName,
|
|
89
|
+
contacts: [{ vcard: generateVCard(contact) }]
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
/** Ready-to-send `contacts` message content for multiple contacts. */
|
|
93
|
+
export const createContactCards = (contacts) => ({
|
|
94
|
+
contacts: {
|
|
95
|
+
displayName: contacts.length === 1 ? contacts[0]?.displayName || contacts[0]?.fullName || '' : `${contacts.length} Contacts`,
|
|
96
|
+
contacts: contacts.map((c) => ({ vcard: generateVCard(c) }))
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
export const quickContact = (name, phone, options) => ({
|
|
100
|
+
fullName: name,
|
|
101
|
+
phones: [{ number: phone, type: 'CELL' }],
|
|
102
|
+
organization: options?.organization,
|
|
103
|
+
emails: options?.email ? [{ email: options.email, type: 'WORK' }] : undefined
|
|
104
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JAP@Add --- Type declarations for the voice-note sender.
|
|
3
|
+
*/
|
|
4
|
+
export interface VoiceNoteBuildOptions {
|
|
5
|
+
convert?: boolean;
|
|
6
|
+
waveform?: number[] | Buffer;
|
|
7
|
+
seconds?: number;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}
|
|
10
|
+
export declare const buildVoiceNoteContent: (audio: Buffer | string | {
|
|
11
|
+
url: string;
|
|
12
|
+
}, opts?: VoiceNoteBuildOptions) => Promise<any>;
|
|
13
|
+
export declare const sendVoiceNote: (sock: any, jid: any, audio: Buffer | string | {
|
|
14
|
+
url: string;
|
|
15
|
+
}, options?: any) => Promise<any>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JAP@Add --- Voice-note (PTT) sender.
|
|
3
|
+
*
|
|
4
|
+
* WhatsApp only plays voice notes encoded as mono 16kHz Opus in OGG. This
|
|
5
|
+
* helper converts anything (mp3/wav/m4a/...) via `MediaManager.convertToVoiceNote`
|
|
6
|
+
* unless the input already is OGG, then sends it with `ptt: true`.
|
|
7
|
+
* Duration + waveform are auto-computed by the media pipeline when the optional
|
|
8
|
+
* `audio-decode` / `music-metadata` packages are installed.
|
|
9
|
+
*
|
|
10
|
+
* ```js
|
|
11
|
+
* await sendVoiceNote(sock, jid, './hello.mp3') // path | Buffer | { url }
|
|
12
|
+
* // or: await sock.sendVoiceNote(jid, './hello.mp3')
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { Boom } from '@hapi/boom';
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
const isOggBuffer = (buf) => Buffer.isBuffer(buf) && buf.length > 4 && buf.subarray(0, 4).toString('latin1') === 'OggS';
|
|
18
|
+
const resolveAudioBuffer = async (audio) => {
|
|
19
|
+
if (Buffer.isBuffer(audio)) {
|
|
20
|
+
return audio;
|
|
21
|
+
}
|
|
22
|
+
const source = typeof audio === 'string' ? audio : audio?.url;
|
|
23
|
+
if (!source || typeof source !== 'string') {
|
|
24
|
+
throw new Boom('sendVoiceNote audio must be a Buffer, a file path, a URL, or { url }', { statusCode: 400 });
|
|
25
|
+
}
|
|
26
|
+
if (/^https?:\/\//i.test(source)) {
|
|
27
|
+
const res = await fetch(source);
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Boom(`sendVoiceNote: fetch failed with HTTP ${res.status}`, { statusCode: 400 });
|
|
30
|
+
}
|
|
31
|
+
return Buffer.from(await res.arrayBuffer());
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return await readFile(source);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
throw new Boom(`sendVoiceNote: cannot read file ${source} (${err?.message || err})`, { statusCode: 400 });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** Build `sendMessage`-ready voice-note content (converts to Opus OGG unless skipped). */
|
|
41
|
+
export const buildVoiceNoteContent = async (audio, { convert = true, waveform, seconds, ...passthrough } = {}) => {
|
|
42
|
+
let buf = await resolveAudioBuffer(audio);
|
|
43
|
+
if (convert && !isOggBuffer(buf)) {
|
|
44
|
+
// Dynamic import: Framework is not imported by Socket/Utils statically (cycle-safe).
|
|
45
|
+
const { MediaManager } = await import('../Framework/MediaManager.js');
|
|
46
|
+
buf = await MediaManager.convertToVoiceNote(buf);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
audio: buf,
|
|
50
|
+
ptt: true,
|
|
51
|
+
mimetype: 'audio/ogg; codecs=opus',
|
|
52
|
+
waveform,
|
|
53
|
+
seconds,
|
|
54
|
+
...passthrough
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/** Standalone sender: `sendVoiceNote(sock, jid, audio, opts?)`. A `sock.sendVoiceNote` alias exists too. */
|
|
58
|
+
export const sendVoiceNote = async (sock, jid, audio, { convert, waveform, seconds, ...sendOptions } = {}) => {
|
|
59
|
+
if (!sock || typeof sock.sendMessage !== 'function') {
|
|
60
|
+
throw new Boom('sendVoiceNote(sock, ...) requires an active Baileys socket', { statusCode: 400 });
|
|
61
|
+
}
|
|
62
|
+
return sock.sendMessage(jid, await buildVoiceNoteContent(audio, { convert, waveform, seconds }), sendOptions);
|
|
63
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class AudioFeeder {
|
|
2
|
+
#private;
|
|
3
|
+
private readonly sampleRate;
|
|
4
|
+
private readonly channels;
|
|
5
|
+
private readonly framesPerChunk;
|
|
6
|
+
private readonly onChunk;
|
|
7
|
+
private readonly source;
|
|
8
|
+
droppedChunks: number;
|
|
9
|
+
underflowChunks: number;
|
|
10
|
+
bytesProduced: number;
|
|
11
|
+
chunksEmitted: number;
|
|
12
|
+
constructor(sampleRate: number, channels: number, framesPerChunk: number, onChunk: (chunk: Float32Array) => void, source?: string | {
|
|
13
|
+
data: Buffer;
|
|
14
|
+
ext?: string;
|
|
15
|
+
});
|
|
16
|
+
start: () => void;
|
|
17
|
+
stop: () => void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio feeder.
|
|
3
|
+
*
|
|
4
|
+
* Spawns ffmpeg to decode `source` into f32le PCM at the requested rate, then
|
|
5
|
+
* meters frames out at chunk-cadence to the WASM uplink.
|
|
6
|
+
*
|
|
7
|
+
* @author J.AP
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { randomBytes } from "node:crypto";
|
|
11
|
+
import { unlinkSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
const LOW_WATERMARK_CHUNKS = 16;
|
|
15
|
+
const MAX_QUEUED_CHUNKS = 1024;
|
|
16
|
+
const DEFAULT_WARMUP_MS = 500;
|
|
17
|
+
export class AudioFeeder {
|
|
18
|
+
sampleRate;
|
|
19
|
+
channels;
|
|
20
|
+
framesPerChunk;
|
|
21
|
+
onChunk;
|
|
22
|
+
source;
|
|
23
|
+
#proc = null;
|
|
24
|
+
#pending = Buffer.alloc(0);
|
|
25
|
+
#tempFile = null; // JAP@Add --- temp copy for { data: Buffer } sources
|
|
26
|
+
#queue = [];
|
|
27
|
+
#emitTimer = null;
|
|
28
|
+
#nextEmitAtMs = 0;
|
|
29
|
+
#warmupUntilMs = 0;
|
|
30
|
+
droppedChunks = 0;
|
|
31
|
+
underflowChunks = 0;
|
|
32
|
+
bytesProduced = 0;
|
|
33
|
+
chunksEmitted = 0;
|
|
34
|
+
constructor(sampleRate, channels, framesPerChunk, onChunk, source = "silence") {
|
|
35
|
+
this.sampleRate = sampleRate;
|
|
36
|
+
this.channels = channels;
|
|
37
|
+
this.framesPerChunk = framesPerChunk;
|
|
38
|
+
this.onChunk = onChunk;
|
|
39
|
+
this.source = source;
|
|
40
|
+
}
|
|
41
|
+
start = () => {
|
|
42
|
+
if (this.#proc)
|
|
43
|
+
return;
|
|
44
|
+
const chunkSamples = this.framesPerChunk * this.channels;
|
|
45
|
+
const chunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
|
|
46
|
+
const chunkIntervalMs = (this.framesPerChunk / this.sampleRate) * 1000;
|
|
47
|
+
const inputArgs = this.#resolveInputArgs();
|
|
48
|
+
this.#proc = spawn("ffmpeg", [
|
|
49
|
+
"-hide_banner",
|
|
50
|
+
"-loglevel", "error",
|
|
51
|
+
"-thread_queue_size", "512",
|
|
52
|
+
...inputArgs,
|
|
53
|
+
"-f", "f32le",
|
|
54
|
+
"-ac", String(this.channels),
|
|
55
|
+
"-ar", String(this.sampleRate),
|
|
56
|
+
"pipe:1",
|
|
57
|
+
]);
|
|
58
|
+
this.#proc.stdout.on("data", (chunk) => {
|
|
59
|
+
this.#pending = Buffer.concat([this.#pending, chunk]);
|
|
60
|
+
while (this.#pending.length >= chunkBytes) {
|
|
61
|
+
if (this.#queue.length >= MAX_QUEUED_CHUNKS) {
|
|
62
|
+
this.#proc?.stdout.pause();
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
const frame = this.#pending.subarray(0, chunkBytes);
|
|
66
|
+
this.#pending = this.#pending.subarray(chunkBytes);
|
|
67
|
+
const out = new Float32Array(chunkSamples);
|
|
68
|
+
out.set(new Float32Array(frame.buffer, frame.byteOffset, chunkSamples));
|
|
69
|
+
this.bytesProduced += chunkBytes;
|
|
70
|
+
this.#queue.push(out);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// JAP@Add --- clear error when ffmpeg is not installed
|
|
74
|
+
this.#proc.on("error", (err) => {
|
|
75
|
+
if (err?.code === "ENOENT") {
|
|
76
|
+
process.stderr.write('[AudioFeeder] ffmpeg binary not found — install ffmpeg to feed call audio (or use audioSource: "silence").\n');
|
|
77
|
+
}
|
|
78
|
+
this.#proc = null;
|
|
79
|
+
});
|
|
80
|
+
this.#proc.stderr.on("data", (chunk) => {
|
|
81
|
+
process.stderr.write(`[AudioFeeder] ${chunk.toString().trim()}\n`);
|
|
82
|
+
});
|
|
83
|
+
this.#proc.on("exit", (code) => {
|
|
84
|
+
if (code !== 0 && code !== null) {
|
|
85
|
+
process.stderr.write(`[AudioFeeder] ffmpeg exited with code=${code}\n`);
|
|
86
|
+
}
|
|
87
|
+
this.#proc = null;
|
|
88
|
+
});
|
|
89
|
+
this.#nextEmitAtMs = 0;
|
|
90
|
+
this.#warmupUntilMs = Date.now() + DEFAULT_WARMUP_MS;
|
|
91
|
+
this.#scheduleNext(chunkSamples, chunkIntervalMs);
|
|
92
|
+
};
|
|
93
|
+
stop = () => {
|
|
94
|
+
if (this.#emitTimer) {
|
|
95
|
+
clearTimeout(this.#emitTimer);
|
|
96
|
+
this.#emitTimer = null;
|
|
97
|
+
}
|
|
98
|
+
this.#proc?.kill("SIGTERM");
|
|
99
|
+
this.#proc = null;
|
|
100
|
+
this.#pending = Buffer.alloc(0);
|
|
101
|
+
this.#queue = [];
|
|
102
|
+
this.#warmupUntilMs = 0;
|
|
103
|
+
// JAP@Add --- remove temp file created for Buffer sources
|
|
104
|
+
if (this.#tempFile) {
|
|
105
|
+
try {
|
|
106
|
+
unlinkSync(this.#tempFile);
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
this.#tempFile = null;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
#resolveInputArgs = () => {
|
|
113
|
+
if (!this.source || this.source === "silence") {
|
|
114
|
+
return ["-f", "lavfi", "-i", `aevalsrc=0:d=3600:s=${this.sampleRate}`];
|
|
115
|
+
}
|
|
116
|
+
// JAP@Add --- in-memory audio: { data: Buffer, ext: 'mp3' } via temp file
|
|
117
|
+
if (this.source && typeof this.source === "object" && Buffer.isBuffer(this.source.data)) {
|
|
118
|
+
const ext = String(this.source.ext || "ogg").replace(/[^a-z0-9]/gi, "") || "ogg";
|
|
119
|
+
const tmp = join(tmpdir(), `jap-voip-${Date.now()}-${randomBytes(4).toString("hex")}.${ext}`);
|
|
120
|
+
writeFileSync(tmp, this.source.data);
|
|
121
|
+
this.#tempFile = tmp;
|
|
122
|
+
return ["-i", tmp];
|
|
123
|
+
}
|
|
124
|
+
if (typeof this.source === "string" && this.source.startsWith("lavfi:")) {
|
|
125
|
+
return ["-f", "lavfi", "-i", this.source.slice("lavfi:".length)];
|
|
126
|
+
}
|
|
127
|
+
return ["-i", this.source];
|
|
128
|
+
};
|
|
129
|
+
#scheduleNext = (chunkSamples, chunkIntervalMs) => {
|
|
130
|
+
if (!this.#proc)
|
|
131
|
+
return;
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
if (this.#nextEmitAtMs === 0)
|
|
134
|
+
this.#nextEmitAtMs = now;
|
|
135
|
+
const delayMs = Math.max(0, this.#nextEmitAtMs - now);
|
|
136
|
+
this.#emitTimer = setTimeout(() => {
|
|
137
|
+
this.#emitTimer = null;
|
|
138
|
+
if (this.#queue.length < LOW_WATERMARK_CHUNKS && Date.now() < this.#warmupUntilMs) {
|
|
139
|
+
this.#nextEmitAtMs = Date.now() + 10;
|
|
140
|
+
this.#scheduleNext(chunkSamples, chunkIntervalMs);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.#flushOne(chunkSamples);
|
|
144
|
+
this.#nextEmitAtMs += chunkIntervalMs;
|
|
145
|
+
this.#scheduleNext(chunkSamples, chunkIntervalMs);
|
|
146
|
+
}, delayMs);
|
|
147
|
+
};
|
|
148
|
+
#flushOne = (chunkSamples) => {
|
|
149
|
+
let nextChunk = this.#queue.shift();
|
|
150
|
+
if (!nextChunk) {
|
|
151
|
+
nextChunk = new Float32Array(chunkSamples);
|
|
152
|
+
this.underflowChunks += 1;
|
|
153
|
+
}
|
|
154
|
+
this.chunksEmitted += 1;
|
|
155
|
+
this.onChunk(nextChunk);
|
|
156
|
+
if (this.#proc?.stdout.isPaused() && this.#queue.length <= MAX_QUEUED_CHUNKS / 4) {
|
|
157
|
+
this.#proc.stdout.resume();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JAP@Add --- Type declarations for the VoIP WAV recorder.
|
|
3
|
+
*/
|
|
4
|
+
export interface WavRecorderOptions {
|
|
5
|
+
sampleRate?: number;
|
|
6
|
+
channels?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface WavRecorder {
|
|
9
|
+
write(pcm: Float32Array): void;
|
|
10
|
+
close(): void;
|
|
11
|
+
readonly bytesWritten: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const createWavRecorder: (filePath: string, opts?: WavRecorderOptions) => WavRecorder;
|