@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,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waliwa - Message send/receive operations
|
|
3
|
+
* High-level API untuk kirim dan terima pesan
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
MessageContent,
|
|
8
|
+
WAMessage,
|
|
9
|
+
WAMessageKey,
|
|
10
|
+
JIDLike,
|
|
11
|
+
SendMessageResult,
|
|
12
|
+
WALogger,
|
|
13
|
+
MessageStatus
|
|
14
|
+
} from '../types/index.js';
|
|
15
|
+
import {
|
|
16
|
+
encodeMessageNode,
|
|
17
|
+
decodeMessageNode,
|
|
18
|
+
generateMessageId,
|
|
19
|
+
generateStanzaId
|
|
20
|
+
} from './message-encoder.js';
|
|
21
|
+
import { encodeBinaryNode, BinaryNode } from '../core/binary.js';
|
|
22
|
+
import { jidToString, isGroupJid } from '../utils/jid.js';
|
|
23
|
+
import { SilentLogger } from '../utils/logger.js';
|
|
24
|
+
|
|
25
|
+
// ============== Send Message Result Tracking ==============
|
|
26
|
+
|
|
27
|
+
interface PendingMessage {
|
|
28
|
+
jid: string;
|
|
29
|
+
messageId: string;
|
|
30
|
+
resolve: (result: SendMessageResult) => void;
|
|
31
|
+
reject: (err: Error) => void;
|
|
32
|
+
timeout: NodeJS.Timeout;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Track pending message sends untuk kaitin response dengan server ACK
|
|
37
|
+
*/
|
|
38
|
+
export class PendingMessageTracker {
|
|
39
|
+
private pending: Map<string, PendingMessage> = new Map();
|
|
40
|
+
|
|
41
|
+
add(jid: string, messageId: string, resolve: (r: SendMessageResult) => void, reject: (e: Error) => void, timeoutMs: number = 30000): void {
|
|
42
|
+
const timeout = setTimeout(() => {
|
|
43
|
+
this.pending.delete(messageId);
|
|
44
|
+
reject(new Error(`Message send timeout for ${messageId}`));
|
|
45
|
+
}, timeoutMs);
|
|
46
|
+
|
|
47
|
+
this.pending.set(messageId, { jid, messageId, resolve, reject, timeout });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
resolve(messageId: string, result: SendMessageResult): void {
|
|
51
|
+
const p = this.pending.get(messageId);
|
|
52
|
+
if (p) {
|
|
53
|
+
clearTimeout(p.timeout);
|
|
54
|
+
this.pending.delete(messageId);
|
|
55
|
+
p.resolve(result);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
reject(messageId: string, err: Error): void {
|
|
60
|
+
const p = this.pending.get(messageId);
|
|
61
|
+
if (p) {
|
|
62
|
+
clearTimeout(p.timeout);
|
|
63
|
+
this.pending.delete(messageId);
|
|
64
|
+
p.reject(err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Clear all pending (untuk disconnect) */
|
|
69
|
+
rejectAll(err: Error): void {
|
|
70
|
+
for (const [, p] of this.pending) {
|
|
71
|
+
clearTimeout(p.timeout);
|
|
72
|
+
p.reject(err);
|
|
73
|
+
}
|
|
74
|
+
this.pending.clear();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
size(): number {
|
|
78
|
+
return this.pending.size;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============== Message Sender ==============
|
|
83
|
+
|
|
84
|
+
export interface MessageSenderOptions {
|
|
85
|
+
/** Function untuk kirim raw bytes via WebSocket */
|
|
86
|
+
send: (data: Uint8Array) => void;
|
|
87
|
+
/** Logger */
|
|
88
|
+
logger?: WALogger;
|
|
89
|
+
/** Track pending sends */
|
|
90
|
+
tracker?: PendingMessageTracker;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class MessageSender {
|
|
94
|
+
private send: (data: Uint8Array) => void;
|
|
95
|
+
private logger: WALogger;
|
|
96
|
+
private tracker: PendingMessageTracker;
|
|
97
|
+
|
|
98
|
+
constructor(opts: MessageSenderOptions) {
|
|
99
|
+
this.send = opts.send;
|
|
100
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
101
|
+
this.tracker = opts.tracker ?? new PendingMessageTracker();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Send message ke recipient
|
|
106
|
+
*/
|
|
107
|
+
async sendMessage(
|
|
108
|
+
jid: JIDLike,
|
|
109
|
+
content: MessageContent,
|
|
110
|
+
options: {
|
|
111
|
+
messageId?: string;
|
|
112
|
+
participant?: string;
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
} = {}
|
|
115
|
+
): Promise<SendMessageResult> {
|
|
116
|
+
const messageId = options.messageId ?? generateMessageId();
|
|
117
|
+
const jidStr = jidToString(jid);
|
|
118
|
+
|
|
119
|
+
this.logger.debug(`Sending message ${messageId} to ${jidStr}`);
|
|
120
|
+
|
|
121
|
+
// Build message node
|
|
122
|
+
const messageNode = encodeMessageNode(jid, content, {
|
|
123
|
+
messageId,
|
|
124
|
+
participant: options.participant
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Wrap dalam <message> envelope
|
|
128
|
+
const envelope: BinaryNode = {
|
|
129
|
+
tag: 'message',
|
|
130
|
+
attrs: {
|
|
131
|
+
to: jidStr,
|
|
132
|
+
type: 'text',
|
|
133
|
+
id: messageId
|
|
134
|
+
},
|
|
135
|
+
content: [messageNode]
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const binaryData = encodeBinaryNode(envelope);
|
|
139
|
+
|
|
140
|
+
// Promise yang resolve ketika server ACK
|
|
141
|
+
return new Promise<SendMessageResult>((resolve, reject) => {
|
|
142
|
+
this.tracker.add(jidStr, messageId, resolve, reject, options.timeoutMs);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
this.send(binaryData);
|
|
146
|
+
} catch (err: any) {
|
|
147
|
+
this.tracker.reject(messageId, err);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Forward message ke recipient lain
|
|
154
|
+
*/
|
|
155
|
+
async forwardMessage(
|
|
156
|
+
to: JIDLike,
|
|
157
|
+
message: WAMessage,
|
|
158
|
+
options: { timeoutMs?: number } = {}
|
|
159
|
+
): Promise<SendMessageResult> {
|
|
160
|
+
const messageId = generateMessageId();
|
|
161
|
+
const jidStr = jidToString(to);
|
|
162
|
+
|
|
163
|
+
this.logger.debug(`Forwarding message ${message.key.id} -> ${jidStr}`);
|
|
164
|
+
|
|
165
|
+
// Build forward node: <message to=... type=... id=...>
|
|
166
|
+
// dengan contextInfo.forwardingScore dan original message
|
|
167
|
+
const forwardNode: BinaryNode = {
|
|
168
|
+
tag: 'message',
|
|
169
|
+
attrs: {
|
|
170
|
+
to: jidStr,
|
|
171
|
+
type: 'text',
|
|
172
|
+
id: messageId
|
|
173
|
+
},
|
|
174
|
+
content: [
|
|
175
|
+
message.message?.text ? {
|
|
176
|
+
tag: 'conversation',
|
|
177
|
+
attrs: {},
|
|
178
|
+
content: message.message.text
|
|
179
|
+
} : null,
|
|
180
|
+
{
|
|
181
|
+
tag: 'contextInfo',
|
|
182
|
+
attrs: {},
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
tag: 'stanzaId',
|
|
186
|
+
attrs: {},
|
|
187
|
+
content: message.key.id
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
tag: 'participant',
|
|
191
|
+
attrs: {},
|
|
192
|
+
content: jidToString(message.key.participant ?? message.key.remoteJid)
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
tag: 'forwardingScore',
|
|
196
|
+
attrs: {},
|
|
197
|
+
content: '1'
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
].filter(Boolean) as any[]
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const binaryData = encodeBinaryNode(forwardNode);
|
|
205
|
+
|
|
206
|
+
return new Promise<SendMessageResult>((resolve, reject) => {
|
|
207
|
+
this.tracker.add(jidStr, messageId, resolve, reject, options.timeoutMs);
|
|
208
|
+
try {
|
|
209
|
+
this.send(binaryData);
|
|
210
|
+
} catch (err: any) {
|
|
211
|
+
this.tracker.reject(messageId, err);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Delete message (revoke)
|
|
218
|
+
*/
|
|
219
|
+
async deleteMessage(
|
|
220
|
+
jid: JIDLike,
|
|
221
|
+
key: WAMessageKey,
|
|
222
|
+
options: { revokeEveryone?: boolean; timeoutMs?: number } = {}
|
|
223
|
+
): Promise<SendMessageResult> {
|
|
224
|
+
const messageId = generateMessageId();
|
|
225
|
+
const jidStr = jidToString(jid);
|
|
226
|
+
|
|
227
|
+
const revokeNode: BinaryNode = {
|
|
228
|
+
tag: 'message',
|
|
229
|
+
attrs: {
|
|
230
|
+
to: jidStr,
|
|
231
|
+
type: 'revoke',
|
|
232
|
+
id: messageId
|
|
233
|
+
},
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
tag: 'revokeMessage',
|
|
237
|
+
attrs: {
|
|
238
|
+
id: key.id,
|
|
239
|
+
fromMe: key.fromMe ? 'true' : 'false',
|
|
240
|
+
participant: jidToString(key.participant ?? key.remoteJid),
|
|
241
|
+
type: options.revokeEveryone ? 'everyone' : 'me'
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const binaryData = encodeBinaryNode(revokeNode);
|
|
248
|
+
|
|
249
|
+
return new Promise<SendMessageResult>((resolve, reject) => {
|
|
250
|
+
this.tracker.add(jidStr, messageId, resolve, reject, options.timeoutMs);
|
|
251
|
+
try {
|
|
252
|
+
this.send(binaryData);
|
|
253
|
+
} catch (err: any) {
|
|
254
|
+
this.tracker.reject(messageId, err);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* React ke message dengan emoji
|
|
261
|
+
*/
|
|
262
|
+
async reactToMessage(
|
|
263
|
+
jid: JIDLike,
|
|
264
|
+
key: WAMessageKey,
|
|
265
|
+
emoji: string,
|
|
266
|
+
options: { timeoutMs?: number } = {}
|
|
267
|
+
): Promise<SendMessageResult> {
|
|
268
|
+
const messageId = generateMessageId();
|
|
269
|
+
const jidStr = jidToString(jid);
|
|
270
|
+
|
|
271
|
+
const reactionNode: BinaryNode = {
|
|
272
|
+
tag: 'message',
|
|
273
|
+
attrs: {
|
|
274
|
+
to: jidStr,
|
|
275
|
+
type: 'reaction',
|
|
276
|
+
id: messageId
|
|
277
|
+
},
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
tag: 'reactionMessage',
|
|
281
|
+
attrs: {
|
|
282
|
+
key: JSON.stringify({
|
|
283
|
+
id: key.id,
|
|
284
|
+
fromMe: key.fromMe,
|
|
285
|
+
remoteJid: jidToString(key.remoteJid),
|
|
286
|
+
participant: key.participant ? jidToString(key.participant) : undefined
|
|
287
|
+
}),
|
|
288
|
+
text: emoji,
|
|
289
|
+
timestamp: Math.floor(Date.now() / 1000).toString()
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const binaryData = encodeBinaryNode(reactionNode);
|
|
296
|
+
|
|
297
|
+
return new Promise<SendMessageResult>((resolve, reject) => {
|
|
298
|
+
this.tracker.add(jidStr, messageId, resolve, reject, options.timeoutMs);
|
|
299
|
+
try {
|
|
300
|
+
this.send(binaryData);
|
|
301
|
+
} catch (err: any) {
|
|
302
|
+
this.tracker.reject(messageId, err);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Update tracker ketika ACK diterima
|
|
309
|
+
*/
|
|
310
|
+
handleAck(messageId: string, status: MessageStatus, timestamp?: number): void {
|
|
311
|
+
this.tracker.resolve(messageId, {
|
|
312
|
+
key: {
|
|
313
|
+
remoteJid: '',
|
|
314
|
+
fromMe: true,
|
|
315
|
+
id: messageId
|
|
316
|
+
},
|
|
317
|
+
status,
|
|
318
|
+
messageTimestamp: timestamp
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Reject all pending (untuk disconnect)
|
|
324
|
+
*/
|
|
325
|
+
destroy(): void {
|
|
326
|
+
this.tracker.rejectAll(new Error('Connection closed'));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ============== Message Receiver ==============
|
|
331
|
+
|
|
332
|
+
export interface MessageReceiverOptions {
|
|
333
|
+
logger?: WALogger;
|
|
334
|
+
/** Handler untuk incoming messages */
|
|
335
|
+
onMessage?: (messages: WAMessage[], type: 'append' | 'notify') => void;
|
|
336
|
+
/** Handler untuk status updates */
|
|
337
|
+
onStatusUpdate?: (key: WAMessageKey, status: MessageStatus) => void;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export class MessageReceiver {
|
|
341
|
+
private logger: WALogger;
|
|
342
|
+
private onMessage?: (messages: WAMessage[], type: 'append' | 'notify') => void;
|
|
343
|
+
private onStatusUpdate?: (key: WAMessageKey, status: MessageStatus) => void;
|
|
344
|
+
|
|
345
|
+
constructor(opts: MessageReceiverOptions) {
|
|
346
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
347
|
+
this.onMessage = opts.onMessage;
|
|
348
|
+
this.onStatusUpdate = opts.onStatusUpdate;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Handle incoming binary node (dari WebSocket)
|
|
353
|
+
*/
|
|
354
|
+
handleNode(node: BinaryNode): void {
|
|
355
|
+
if (!node) return;
|
|
356
|
+
|
|
357
|
+
switch (node.tag) {
|
|
358
|
+
case 'message':
|
|
359
|
+
this.handleMessageNode(node);
|
|
360
|
+
break;
|
|
361
|
+
case 'receipt':
|
|
362
|
+
this.handleReceiptNode(node);
|
|
363
|
+
break;
|
|
364
|
+
case 'status':
|
|
365
|
+
this.handleStatusNode(node);
|
|
366
|
+
break;
|
|
367
|
+
case 'ack':
|
|
368
|
+
this.handleAckNode(node);
|
|
369
|
+
break;
|
|
370
|
+
default:
|
|
371
|
+
// Unknown node type
|
|
372
|
+
this.logger.trace(`Unhandled node: ${node.tag}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private handleMessageNode(node: BinaryNode): void {
|
|
377
|
+
if (!this.onMessage) return;
|
|
378
|
+
|
|
379
|
+
const attrs = node.attrs || {};
|
|
380
|
+
const from = attrs.from ?? '';
|
|
381
|
+
const fromMe = attrs.fromMe === 'true' || attrs.type === 'send';
|
|
382
|
+
const id = attrs.id;
|
|
383
|
+
const type = attrs.type;
|
|
384
|
+
|
|
385
|
+
// Convert ke WAMessage
|
|
386
|
+
const message = decodeMessageNode(node, fromMe);
|
|
387
|
+
if (!message) return;
|
|
388
|
+
|
|
389
|
+
this.logger.debug(`Received message ${id} from ${from} (type: ${type})`);
|
|
390
|
+
|
|
391
|
+
this.onMessage([message], type === 'append' ? 'append' : 'notify');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private handleReceiptNode(node: BinaryNode): void {
|
|
395
|
+
const attrs = node.attrs || {};
|
|
396
|
+
const id = attrs.id;
|
|
397
|
+
const type = attrs.type;
|
|
398
|
+
const from = attrs.from;
|
|
399
|
+
|
|
400
|
+
if (!this.onStatusUpdate || !id) return;
|
|
401
|
+
|
|
402
|
+
const statusMap: Record<string, MessageStatus> = {
|
|
403
|
+
'read': 'read',
|
|
404
|
+
'delivered': 'delivered',
|
|
405
|
+
'played': 'played'
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const status = statusMap[type];
|
|
409
|
+
if (status) {
|
|
410
|
+
this.onStatusUpdate({
|
|
411
|
+
remoteJid: from,
|
|
412
|
+
fromMe: true,
|
|
413
|
+
id
|
|
414
|
+
}, status);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private handleStatusNode(node: BinaryNode): void {
|
|
419
|
+
const attrs = node.attrs || {};
|
|
420
|
+
const id = attrs.id;
|
|
421
|
+
const type = attrs.type;
|
|
422
|
+
const from = attrs.from;
|
|
423
|
+
|
|
424
|
+
if (!this.onStatusUpdate || !id) return;
|
|
425
|
+
|
|
426
|
+
const statusMap: Record<string, MessageStatus> = {
|
|
427
|
+
'read': 'read',
|
|
428
|
+
'delivered': 'delivered',
|
|
429
|
+
'played': 'played'
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const status = statusMap[type];
|
|
433
|
+
if (status) {
|
|
434
|
+
this.onStatusUpdate({
|
|
435
|
+
remoteJid: from,
|
|
436
|
+
fromMe: true,
|
|
437
|
+
id
|
|
438
|
+
}, status);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private handleAckNode(node: BinaryNode): void {
|
|
443
|
+
// ACK dari server untuk confirm message received
|
|
444
|
+
const attrs = node.attrs || {};
|
|
445
|
+
const id = attrs.id;
|
|
446
|
+
const type = attrs.type;
|
|
447
|
+
|
|
448
|
+
if (!id) return;
|
|
449
|
+
|
|
450
|
+
const statusMap: Record<string, MessageStatus> = {
|
|
451
|
+
'received': 'sent',
|
|
452
|
+
'read': 'read',
|
|
453
|
+
'played': 'played'
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const status = statusMap[type] ?? 'sent';
|
|
457
|
+
if (this.onStatusUpdate) {
|
|
458
|
+
this.onStatusUpdate({
|
|
459
|
+
remoteJid: '',
|
|
460
|
+
fromMe: true,
|
|
461
|
+
id
|
|
462
|
+
}, status);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Set handlers (untuk runtime binding)
|
|
468
|
+
*/
|
|
469
|
+
setHandlers(opts: MessageReceiverOptions): void {
|
|
470
|
+
this.onMessage = opts.onMessage;
|
|
471
|
+
this.onStatusUpdate = opts.onStatusUpdate;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ============== Read Receipt Sender ==============
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Send read receipts untuk incoming messages
|
|
479
|
+
*/
|
|
480
|
+
export function buildReadReceiptNode(
|
|
481
|
+
jid: JIDLike,
|
|
482
|
+
messageIds: string[],
|
|
483
|
+
participant?: JIDLike
|
|
484
|
+
): BinaryNode {
|
|
485
|
+
const attrs: any = {
|
|
486
|
+
to: jidToString(jid),
|
|
487
|
+
type: 'read',
|
|
488
|
+
id: messageIds[0]
|
|
489
|
+
};
|
|
490
|
+
if (participant) attrs.participant = jidToString(participant);
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
tag: 'receipt',
|
|
494
|
+
attrs,
|
|
495
|
+
content: messageIds.map(id => ({
|
|
496
|
+
tag: 'receipt',
|
|
497
|
+
attrs: { id, t: Math.floor(Date.now() / 1000).toString() }
|
|
498
|
+
}))
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Send typing indicator
|
|
504
|
+
*/
|
|
505
|
+
export function buildChatPresenceNode(
|
|
506
|
+
jid: JIDLike,
|
|
507
|
+
type: 'composing' | 'paused' | 'recording',
|
|
508
|
+
media?: 'audio' | 'video'
|
|
509
|
+
): BinaryNode {
|
|
510
|
+
return {
|
|
511
|
+
tag: 'chat',
|
|
512
|
+
attrs: {
|
|
513
|
+
to: jidToString(jid),
|
|
514
|
+
type
|
|
515
|
+
},
|
|
516
|
+
content: media ? [{
|
|
517
|
+
tag: media,
|
|
518
|
+
attrs: {}
|
|
519
|
+
}] : undefined
|
|
520
|
+
};
|
|
521
|
+
}
|