@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,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
+ }