@kangwifi-pro/waliwa 1.0.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/README.md +600 -0
- package/dist/advanced/features.d.ts +211 -0
- package/dist/advanced/features.d.ts.map +1 -0
- package/dist/advanced/features.js +671 -0
- package/dist/advanced/features.js.map +1 -0
- package/dist/auth/auth-state.d.ts +89 -0
- package/dist/auth/auth-state.d.ts.map +1 -0
- package/dist/auth/auth-state.js +330 -0
- package/dist/auth/auth-state.js.map +1 -0
- package/dist/auth/pairing-code.d.ts +78 -0
- package/dist/auth/pairing-code.d.ts.map +1 -0
- package/dist/auth/pairing-code.js +191 -0
- package/dist/auth/pairing-code.js.map +1 -0
- package/dist/auth/qr-code.d.ts +60 -0
- package/dist/auth/qr-code.d.ts.map +1 -0
- package/dist/auth/qr-code.js +151 -0
- package/dist/auth/qr-code.js.map +1 -0
- package/dist/calls/handler.d.ts +61 -0
- package/dist/calls/handler.d.ts.map +1 -0
- package/dist/calls/handler.js +117 -0
- package/dist/calls/handler.js.map +1 -0
- package/dist/core/binary.d.ts +74 -0
- package/dist/core/binary.d.ts.map +1 -0
- package/dist/core/binary.js +448 -0
- package/dist/core/binary.js.map +1 -0
- package/dist/core/crypto.d.ts +60 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +170 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/noise.d.ts +106 -0
- package/dist/core/noise.d.ts.map +1 -0
- package/dist/core/noise.js +307 -0
- package/dist/core/noise.js.map +1 -0
- package/dist/events/emitter.d.ts +44 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +79 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/features/index.d.ts +342 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +755 -0
- package/dist/features/index.js.map +1 -0
- package/dist/fixes/index.d.ts +333 -0
- package/dist/fixes/index.d.ts.map +1 -0
- package/dist/fixes/index.js +762 -0
- package/dist/fixes/index.js.map +1 -0
- package/dist/groups/management.d.ts +86 -0
- package/dist/groups/management.d.ts.map +1 -0
- package/dist/groups/management.js +443 -0
- package/dist/groups/management.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/media.d.ts +93 -0
- package/dist/messages/media.d.ts.map +1 -0
- package/dist/messages/media.js +252 -0
- package/dist/messages/media.js.map +1 -0
- package/dist/messages/message-encoder.d.ts +70 -0
- package/dist/messages/message-encoder.d.ts.map +1 -0
- package/dist/messages/message-encoder.js +453 -0
- package/dist/messages/message-encoder.js.map +1 -0
- package/dist/messages/send.d.ts +101 -0
- package/dist/messages/send.d.ts.map +1 -0
- package/dist/messages/send.js +409 -0
- package/dist/messages/send.js.map +1 -0
- package/dist/recovery/index.d.ts +125 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +584 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/skdm/index.d.ts +220 -0
- package/dist/skdm/index.d.ts.map +1 -0
- package/dist/skdm/index.js +600 -0
- package/dist/skdm/index.js.map +1 -0
- package/dist/socket/hybrid.d.ts +118 -0
- package/dist/socket/hybrid.d.ts.map +1 -0
- package/dist/socket/hybrid.js +352 -0
- package/dist/socket/hybrid.js.map +1 -0
- package/dist/socket/wa-socket.d.ts +300 -0
- package/dist/socket/wa-socket.d.ts.map +1 -0
- package/dist/socket/wa-socket.js +1094 -0
- package/dist/socket/wa-socket.js.map +1 -0
- package/dist/socket/ws-socket.d.ts +96 -0
- package/dist/socket/ws-socket.d.ts.map +1 -0
- package/dist/socket/ws-socket.js +302 -0
- package/dist/socket/ws-socket.js.map +1 -0
- package/dist/types/index.d.ts +444 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/jid.d.ts +46 -0
- package/dist/utils/jid.d.ts.map +1 -0
- package/dist/utils/jid.js +152 -0
- package/dist/utils/jid.js.map +1 -0
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +43 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +175 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/API.md +745 -0
- package/docs/ARCHITECTURE.md +307 -0
- package/docs/BAILEYS_FIXES.md +360 -0
- package/docs/FEATURES.md +532 -0
- package/docs/PROTOCOL.md +489 -0
- package/docs/RECOVERY.md +409 -0
- package/docs/SKDM.md +233 -0
- package/examples/ai-bot/index.ts +479 -0
- package/examples/ai-bot/package.json +17 -0
- package/examples/echo-bot/index.ts +171 -0
- package/examples/echo-bot/package.json +18 -0
- package/examples/group-bot/index.ts +557 -0
- package/examples/group-bot/package.json +17 -0
- package/examples/rest-gateway/index.ts +499 -0
- package/examples/rest-gateway/package.json +19 -0
- package/package.json +75 -0
- package/src/advanced/features.ts +817 -0
- package/src/auth/auth-state.ts +342 -0
- package/src/auth/pairing-code.ts +246 -0
- package/src/auth/qr-code.ts +191 -0
- package/src/calls/handler.ts +153 -0
- package/src/core/binary.ts +464 -0
- package/src/core/crypto.ts +189 -0
- package/src/core/noise.ts +406 -0
- package/src/events/emitter.ts +88 -0
- package/src/features/index.ts +921 -0
- package/src/fixes/index.ts +882 -0
- package/src/groups/management.ts +497 -0
- package/src/index.ts +274 -0
- package/src/messages/media.ts +372 -0
- package/src/messages/message-encoder.ts +520 -0
- package/src/messages/send.ts +521 -0
- package/src/recovery/index.ts +704 -0
- package/src/skdm/index.ts +693 -0
- package/src/socket/hybrid.ts +414 -0
- package/src/socket/wa-socket.ts +1347 -0
- package/src/socket/ws-socket.ts +355 -0
- package/src/types/index.ts +457 -0
- package/src/utils/jid.ts +156 -0
- package/src/utils/logger.ts +84 -0
- package/src/utils/retry.ts +205 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waliwa - QR code generation untuk WhatsApp Web Multi-Device pairing
|
|
3
|
+
*
|
|
4
|
+
* QR code berisi: ref + publicKey + pairingStaticKey (encoded sebagai comma-separated)
|
|
5
|
+
* User scan dengan WhatsApp mobile app untuk establish pairing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import qrcode from 'qrcode-terminal';
|
|
9
|
+
import type {
|
|
10
|
+
QRGenerationData,
|
|
11
|
+
AuthenticationCreds,
|
|
12
|
+
WALogger
|
|
13
|
+
} from '../types/index.js';
|
|
14
|
+
import { concatBytes, getRandomBytes } from '../core/crypto.js';
|
|
15
|
+
import { SilentLogger } from '../utils/logger.js';
|
|
16
|
+
|
|
17
|
+
// ============== QR Reference Generator ==============
|
|
18
|
+
|
|
19
|
+
let qrRefCounter = 0;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate new QR reference (server-side correlation ID)
|
|
23
|
+
* Format: 8-character alphanumeric
|
|
24
|
+
*/
|
|
25
|
+
export function generateQRRef(): string {
|
|
26
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
27
|
+
let ref = '';
|
|
28
|
+
const rand = getRandomBytes(8);
|
|
29
|
+
for (let i = 0; i < 8; i++) {
|
|
30
|
+
ref += chars[rand[i] % chars.length];
|
|
31
|
+
}
|
|
32
|
+
qrRefCounter++;
|
|
33
|
+
return ref + qrRefCounter.toString(36);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============== QR Encoder ==============
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build QR code string untuk WA pairing
|
|
40
|
+
*
|
|
41
|
+
* Format:
|
|
42
|
+
* ref,publicKey(base64),pairingStaticKey(base64),clientDid,serverDid
|
|
43
|
+
*
|
|
44
|
+
* WhatsApp mobile app akan scan QR, kirim ke server, server pakai publicKey
|
|
45
|
+
* untuk encrypt pairing payload yang berisi identity keys.
|
|
46
|
+
*/
|
|
47
|
+
export function buildQRString(
|
|
48
|
+
ref: string,
|
|
49
|
+
publicKey: Uint8Array,
|
|
50
|
+
pairingStaticKey: Uint8Array,
|
|
51
|
+
/** Client device ID (noise static key fingerprint) */
|
|
52
|
+
clientDid?: Uint8Array
|
|
53
|
+
): string {
|
|
54
|
+
const pubKeyB64 = Buffer.from(publicKey).toString('base64');
|
|
55
|
+
const pairingB64 = Buffer.from(pairingStaticKey).toString('base64');
|
|
56
|
+
const didB64 = clientDid ? Buffer.from(clientDid).toString('base64') : '';
|
|
57
|
+
|
|
58
|
+
// Format: ref,publicKey,pairingStaticKey,clientDid
|
|
59
|
+
if (didB64) {
|
|
60
|
+
return `${ref},${pubKeyB64},${pairingB64},${didB64}`;
|
|
61
|
+
}
|
|
62
|
+
return `${ref},${pubKeyB64},${pairingB64}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============== QR Generation ==============
|
|
66
|
+
|
|
67
|
+
export interface QRGenerationOptions {
|
|
68
|
+
creds: AuthenticationCreds;
|
|
69
|
+
logger?: WALogger;
|
|
70
|
+
/** Print QR ke terminal? */
|
|
71
|
+
printInTerminal?: boolean;
|
|
72
|
+
/** Small QR mode (lebih kompak di terminal) */
|
|
73
|
+
small?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate QR code data dari auth creds
|
|
78
|
+
*/
|
|
79
|
+
export function generateQRCodeData(opts: QRGenerationOptions): QRGenerationData {
|
|
80
|
+
const logger = opts.logger ?? new SilentLogger();
|
|
81
|
+
|
|
82
|
+
if (!opts.creds.noiseKey) {
|
|
83
|
+
throw new Error('noiseKey tidak tersedia di creds - generate creds baru');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ref = generateQRRef();
|
|
87
|
+
const publicKey = opts.creds.noiseKey.public;
|
|
88
|
+
const pairingStaticKey = opts.creds.pairingKey?.public ?? new Uint8Array(32);
|
|
89
|
+
|
|
90
|
+
const qrString = buildQRString(ref, publicKey, pairingStaticKey);
|
|
91
|
+
|
|
92
|
+
if (opts.printInTerminal) {
|
|
93
|
+
logger.info('Scan QR code ini dengan WhatsApp mobile app:\n');
|
|
94
|
+
qrcode.generate(qrString, { small: opts.small ?? true }, (qr: string) => {
|
|
95
|
+
console.log(qr);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
ref,
|
|
101
|
+
publicKey,
|
|
102
|
+
pairingStaticKey,
|
|
103
|
+
qrString,
|
|
104
|
+
timeout: 60 // 60 seconds default
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============== QR Refresh Manager ==============
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* QR refresh manager - auto-refresh QR setiap ~20 detik (sesuai WA server timeout)
|
|
112
|
+
* Untuk menghindari stale QR yang sudah expire
|
|
113
|
+
*/
|
|
114
|
+
export class QRRefreshManager {
|
|
115
|
+
private timer: NodeJS.Timeout | null = null;
|
|
116
|
+
private onRefresh: (qr: QRGenerationData) => void;
|
|
117
|
+
private onTimeout: () => void;
|
|
118
|
+
private refreshIntervalMs: number;
|
|
119
|
+
private maxRefreshCount: number;
|
|
120
|
+
private refreshCount: number = 0;
|
|
121
|
+
private isRunning: boolean = false;
|
|
122
|
+
|
|
123
|
+
constructor(
|
|
124
|
+
onRefresh: (qr: QRGenerationData) => void,
|
|
125
|
+
onTimeout: () => void,
|
|
126
|
+
refreshIntervalMs: number = 20000,
|
|
127
|
+
maxRefreshCount: number = 5
|
|
128
|
+
) {
|
|
129
|
+
this.onRefresh = onRefresh;
|
|
130
|
+
this.onTimeout = onTimeout;
|
|
131
|
+
this.refreshIntervalMs = refreshIntervalMs;
|
|
132
|
+
this.maxRefreshCount = maxRefreshCount;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
start(initialQR: QRGenerationData): void {
|
|
136
|
+
if (this.isRunning) return;
|
|
137
|
+
this.isRunning = true;
|
|
138
|
+
this.refreshCount = 0;
|
|
139
|
+
this.onRefresh(initialQR);
|
|
140
|
+
|
|
141
|
+
this.timer = setInterval(() => {
|
|
142
|
+
this.refreshCount++;
|
|
143
|
+
if (this.refreshCount >= this.maxRefreshCount) {
|
|
144
|
+
this.stop();
|
|
145
|
+
this.onTimeout();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// Trigger refresh - caller akan generate new QR dan pass via update
|
|
149
|
+
this.onRefresh(this.generateNewQRPlaceholder());
|
|
150
|
+
}, this.refreshIntervalMs);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private generateNewQRPlaceholder(): QRGenerationData {
|
|
154
|
+
// Placeholder - actual generation handled by caller
|
|
155
|
+
return {
|
|
156
|
+
ref: generateQRRef(),
|
|
157
|
+
publicKey: new Uint8Array(32),
|
|
158
|
+
pairingStaticKey: new Uint8Array(32),
|
|
159
|
+
qrString: '',
|
|
160
|
+
timeout: 20
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
stop(): void {
|
|
165
|
+
if (this.timer) {
|
|
166
|
+
clearInterval(this.timer);
|
|
167
|
+
this.timer = null;
|
|
168
|
+
}
|
|
169
|
+
this.isRunning = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ============== QR Decoder (untuk reverse direction / debug) ==============
|
|
174
|
+
|
|
175
|
+
export function parseQRString(qrString: string): {
|
|
176
|
+
ref: string;
|
|
177
|
+
publicKey: Uint8Array;
|
|
178
|
+
pairingStaticKey: Uint8Array;
|
|
179
|
+
clientDid?: Uint8Array;
|
|
180
|
+
} {
|
|
181
|
+
const parts = qrString.split(',');
|
|
182
|
+
if (parts.length < 3) {
|
|
183
|
+
throw new Error('Invalid QR string format');
|
|
184
|
+
}
|
|
185
|
+
const [ref, pubKeyB64, pairingB64, didB64] = parts;
|
|
186
|
+
const publicKey = new Uint8Array(Buffer.from(pubKeyB64, 'base64'));
|
|
187
|
+
const pairingStaticKey = new Uint8Array(Buffer.from(pairingB64, 'base64'));
|
|
188
|
+
const clientDid = didB64 ? new Uint8Array(Buffer.from(didB64, 'base64')) : undefined;
|
|
189
|
+
|
|
190
|
+
return { ref, publicKey, pairingStaticKey, clientDid };
|
|
191
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waliwa - Call handling
|
|
3
|
+
* Process incoming WhatsApp call events (offer, reject, terminate)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
WACallEvent,
|
|
8
|
+
CallStatus,
|
|
9
|
+
JIDLike,
|
|
10
|
+
WALogger
|
|
11
|
+
} from '../types/index.js';
|
|
12
|
+
import { encodeBinaryNode, BinaryNode } from '../core/binary.js';
|
|
13
|
+
import { jidToString } from '../utils/jid.js';
|
|
14
|
+
import { SilentLogger } from '../utils/logger.js';
|
|
15
|
+
|
|
16
|
+
export interface CallHandlerOptions {
|
|
17
|
+
send: (data: Uint8Array) => void;
|
|
18
|
+
logger?: WALogger;
|
|
19
|
+
onCall?: (event: WACallEvent) => void;
|
|
20
|
+
/** Auto reject incoming calls */
|
|
21
|
+
autoReject?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle incoming WhatsApp call events
|
|
26
|
+
* Calls masuk sebagai binary node dengan tag "call"
|
|
27
|
+
*/
|
|
28
|
+
export class CallHandler {
|
|
29
|
+
private send: (data: Uint8Array) => void;
|
|
30
|
+
private logger: WALogger;
|
|
31
|
+
private onCall?: (event: WACallEvent) => void;
|
|
32
|
+
private autoReject: boolean;
|
|
33
|
+
|
|
34
|
+
constructor(opts: CallHandlerOptions) {
|
|
35
|
+
this.send = opts.send;
|
|
36
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
37
|
+
this.onCall = opts.onCall;
|
|
38
|
+
this.autoReject = opts.autoReject ?? false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Handle call node
|
|
43
|
+
*/
|
|
44
|
+
async handleCallNode(node: BinaryNode): Promise<void> {
|
|
45
|
+
const attrs = node.attrs || {};
|
|
46
|
+
const from = attrs.from;
|
|
47
|
+
const callId = attrs.id;
|
|
48
|
+
const isVideo = attrs.video === 'true';
|
|
49
|
+
const isGroup = attrs.group === 'true' || attrs.is_group === 'true';
|
|
50
|
+
const status = attrs.status as CallStatus || 'ringing';
|
|
51
|
+
|
|
52
|
+
const event: WACallEvent = {
|
|
53
|
+
id: callId,
|
|
54
|
+
from,
|
|
55
|
+
to: attrs.to,
|
|
56
|
+
isVideo,
|
|
57
|
+
isGroup,
|
|
58
|
+
status,
|
|
59
|
+
timestamp: Date.now(),
|
|
60
|
+
offer: attrs.offer
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
this.logger.info(`Incoming ${isVideo ? 'video' : 'voice'} call from ${from} (id: ${callId})`);
|
|
64
|
+
if (this.onCall) this.onCall(event);
|
|
65
|
+
|
|
66
|
+
// Auto reject jika enabled
|
|
67
|
+
if (this.autoReject && status === 'ringing') {
|
|
68
|
+
await this.rejectCall(callId, from);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reject incoming call
|
|
74
|
+
*/
|
|
75
|
+
async rejectCall(callId: string, from: JIDLike): Promise<void> {
|
|
76
|
+
const node: BinaryNode = {
|
|
77
|
+
tag: 'call',
|
|
78
|
+
attrs: {
|
|
79
|
+
id: callId,
|
|
80
|
+
from: attrs_to_placeholder(),
|
|
81
|
+
to: jidToString(from),
|
|
82
|
+
action: 'reject',
|
|
83
|
+
type: 'reject'
|
|
84
|
+
},
|
|
85
|
+
content: undefined
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
this.send(encodeBinaryNode(node));
|
|
89
|
+
this.logger.info(`Rejected call ${callId}`);
|
|
90
|
+
} catch (err: any) {
|
|
91
|
+
this.logger.error(`Failed to reject call: ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Accept incoming call (voice only, no WebRTC support)
|
|
97
|
+
*/
|
|
98
|
+
async acceptCall(callId: string, from: JIDLike): Promise<void> {
|
|
99
|
+
const node: BinaryNode = {
|
|
100
|
+
tag: 'call',
|
|
101
|
+
attrs: {
|
|
102
|
+
id: callId,
|
|
103
|
+
to: jidToString(from),
|
|
104
|
+
action: 'accept',
|
|
105
|
+
type: 'accept'
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
try {
|
|
109
|
+
this.send(encodeBinaryNode(node));
|
|
110
|
+
this.logger.info(`Accepted call ${callId}`);
|
|
111
|
+
} catch (err: any) {
|
|
112
|
+
this.logger.error(`Failed to accept call: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Set call handler
|
|
118
|
+
*/
|
|
119
|
+
setHandler(handler: (event: WACallEvent) => void): void {
|
|
120
|
+
this.onCall = handler;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Enable/disable auto reject
|
|
125
|
+
*/
|
|
126
|
+
setAutoReject(enabled: boolean): void {
|
|
127
|
+
this.autoReject = enabled;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Helper placeholder for "to" field (own JID)
|
|
132
|
+
function attrs_to_placeholder(): string {
|
|
133
|
+
// Should be set to own JID
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Voice message transcription hook
|
|
139
|
+
* Note: WhatsApp punya transcription API tapi memerlukan server-side processing
|
|
140
|
+
*/
|
|
141
|
+
export interface VoiceTranscriptionResult {
|
|
142
|
+
callId?: string;
|
|
143
|
+
messageId?: string;
|
|
144
|
+
text: string;
|
|
145
|
+
language?: string;
|
|
146
|
+
confidence?: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Transcribe voice message via external service (hook)
|
|
151
|
+
* User harus implement sendiri atau pakai service seperti Whisper
|
|
152
|
+
*/
|
|
153
|
+
export type VoiceTranscriptionFn = (audioBuffer: Buffer) => Promise<VoiceTranscriptionResult | null>;
|