@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.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +600 -0
  3. package/dist/advanced/features.d.ts +211 -0
  4. package/dist/advanced/features.d.ts.map +1 -0
  5. package/dist/advanced/features.js +671 -0
  6. package/dist/advanced/features.js.map +1 -0
  7. package/dist/auth/auth-state.d.ts +89 -0
  8. package/dist/auth/auth-state.d.ts.map +1 -0
  9. package/dist/auth/auth-state.js +330 -0
  10. package/dist/auth/auth-state.js.map +1 -0
  11. package/dist/auth/pairing-code.d.ts +78 -0
  12. package/dist/auth/pairing-code.d.ts.map +1 -0
  13. package/dist/auth/pairing-code.js +191 -0
  14. package/dist/auth/pairing-code.js.map +1 -0
  15. package/dist/auth/qr-code.d.ts +60 -0
  16. package/dist/auth/qr-code.d.ts.map +1 -0
  17. package/dist/auth/qr-code.js +151 -0
  18. package/dist/auth/qr-code.js.map +1 -0
  19. package/dist/calls/handler.d.ts +61 -0
  20. package/dist/calls/handler.d.ts.map +1 -0
  21. package/dist/calls/handler.js +117 -0
  22. package/dist/calls/handler.js.map +1 -0
  23. package/dist/core/binary.d.ts +74 -0
  24. package/dist/core/binary.d.ts.map +1 -0
  25. package/dist/core/binary.js +448 -0
  26. package/dist/core/binary.js.map +1 -0
  27. package/dist/core/crypto.d.ts +60 -0
  28. package/dist/core/crypto.d.ts.map +1 -0
  29. package/dist/core/crypto.js +170 -0
  30. package/dist/core/crypto.js.map +1 -0
  31. package/dist/core/noise.d.ts +106 -0
  32. package/dist/core/noise.d.ts.map +1 -0
  33. package/dist/core/noise.js +307 -0
  34. package/dist/core/noise.js.map +1 -0
  35. package/dist/events/emitter.d.ts +44 -0
  36. package/dist/events/emitter.d.ts.map +1 -0
  37. package/dist/events/emitter.js +79 -0
  38. package/dist/events/emitter.js.map +1 -0
  39. package/dist/features/index.d.ts +342 -0
  40. package/dist/features/index.d.ts.map +1 -0
  41. package/dist/features/index.js +755 -0
  42. package/dist/features/index.js.map +1 -0
  43. package/dist/fixes/index.d.ts +333 -0
  44. package/dist/fixes/index.d.ts.map +1 -0
  45. package/dist/fixes/index.js +762 -0
  46. package/dist/fixes/index.js.map +1 -0
  47. package/dist/groups/management.d.ts +86 -0
  48. package/dist/groups/management.d.ts.map +1 -0
  49. package/dist/groups/management.js +443 -0
  50. package/dist/groups/management.js.map +1 -0
  51. package/dist/index.d.ts +65 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +236 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/messages/media.d.ts +93 -0
  56. package/dist/messages/media.d.ts.map +1 -0
  57. package/dist/messages/media.js +252 -0
  58. package/dist/messages/media.js.map +1 -0
  59. package/dist/messages/message-encoder.d.ts +70 -0
  60. package/dist/messages/message-encoder.d.ts.map +1 -0
  61. package/dist/messages/message-encoder.js +453 -0
  62. package/dist/messages/message-encoder.js.map +1 -0
  63. package/dist/messages/send.d.ts +101 -0
  64. package/dist/messages/send.d.ts.map +1 -0
  65. package/dist/messages/send.js +409 -0
  66. package/dist/messages/send.js.map +1 -0
  67. package/dist/recovery/index.d.ts +125 -0
  68. package/dist/recovery/index.d.ts.map +1 -0
  69. package/dist/recovery/index.js +584 -0
  70. package/dist/recovery/index.js.map +1 -0
  71. package/dist/skdm/index.d.ts +220 -0
  72. package/dist/skdm/index.d.ts.map +1 -0
  73. package/dist/skdm/index.js +600 -0
  74. package/dist/skdm/index.js.map +1 -0
  75. package/dist/socket/hybrid.d.ts +118 -0
  76. package/dist/socket/hybrid.d.ts.map +1 -0
  77. package/dist/socket/hybrid.js +352 -0
  78. package/dist/socket/hybrid.js.map +1 -0
  79. package/dist/socket/wa-socket.d.ts +300 -0
  80. package/dist/socket/wa-socket.d.ts.map +1 -0
  81. package/dist/socket/wa-socket.js +1094 -0
  82. package/dist/socket/wa-socket.js.map +1 -0
  83. package/dist/socket/ws-socket.d.ts +96 -0
  84. package/dist/socket/ws-socket.d.ts.map +1 -0
  85. package/dist/socket/ws-socket.js +302 -0
  86. package/dist/socket/ws-socket.js.map +1 -0
  87. package/dist/types/index.d.ts +444 -0
  88. package/dist/types/index.d.ts.map +1 -0
  89. package/dist/types/index.js +7 -0
  90. package/dist/types/index.js.map +1 -0
  91. package/dist/utils/jid.d.ts +46 -0
  92. package/dist/utils/jid.d.ts.map +1 -0
  93. package/dist/utils/jid.js +152 -0
  94. package/dist/utils/jid.js.map +1 -0
  95. package/dist/utils/logger.d.ts +45 -0
  96. package/dist/utils/logger.d.ts.map +1 -0
  97. package/dist/utils/logger.js +79 -0
  98. package/dist/utils/logger.js.map +1 -0
  99. package/dist/utils/retry.d.ts +43 -0
  100. package/dist/utils/retry.d.ts.map +1 -0
  101. package/dist/utils/retry.js +175 -0
  102. package/dist/utils/retry.js.map +1 -0
  103. package/docs/API.md +745 -0
  104. package/docs/ARCHITECTURE.md +307 -0
  105. package/docs/BAILEYS_FIXES.md +360 -0
  106. package/docs/FEATURES.md +532 -0
  107. package/docs/PROTOCOL.md +489 -0
  108. package/docs/RECOVERY.md +409 -0
  109. package/docs/SKDM.md +233 -0
  110. package/examples/ai-bot/index.ts +479 -0
  111. package/examples/ai-bot/package.json +17 -0
  112. package/examples/echo-bot/index.ts +171 -0
  113. package/examples/echo-bot/package.json +18 -0
  114. package/examples/group-bot/index.ts +557 -0
  115. package/examples/group-bot/package.json +17 -0
  116. package/examples/rest-gateway/index.ts +499 -0
  117. package/examples/rest-gateway/package.json +19 -0
  118. package/package.json +75 -0
  119. package/src/advanced/features.ts +817 -0
  120. package/src/auth/auth-state.ts +342 -0
  121. package/src/auth/pairing-code.ts +246 -0
  122. package/src/auth/qr-code.ts +191 -0
  123. package/src/calls/handler.ts +153 -0
  124. package/src/core/binary.ts +464 -0
  125. package/src/core/crypto.ts +189 -0
  126. package/src/core/noise.ts +406 -0
  127. package/src/events/emitter.ts +88 -0
  128. package/src/features/index.ts +921 -0
  129. package/src/fixes/index.ts +882 -0
  130. package/src/groups/management.ts +497 -0
  131. package/src/index.ts +274 -0
  132. package/src/messages/media.ts +372 -0
  133. package/src/messages/message-encoder.ts +520 -0
  134. package/src/messages/send.ts +521 -0
  135. package/src/recovery/index.ts +704 -0
  136. package/src/skdm/index.ts +693 -0
  137. package/src/socket/hybrid.ts +414 -0
  138. package/src/socket/wa-socket.ts +1347 -0
  139. package/src/socket/ws-socket.ts +355 -0
  140. package/src/types/index.ts +457 -0
  141. package/src/utils/jid.ts +156 -0
  142. package/src/utils/logger.ts +84 -0
  143. package/src/utils/retry.ts +205 -0
  144. 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>;