@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,520 @@
1
+ /**
2
+ * Waliwa - Message encoding/decoding
3
+ * Encode MessageContent ke WhatsApp protobuf Message format
4
+ * Decode incoming WA Message ke MessageContent
5
+ *
6
+ * Hybrid approach: manual binary encoding untuk messages (lebih hemat RAM),
7
+ * pakai protobufjs untuk complex nested message types
8
+ */
9
+
10
+ import type {
11
+ MessageContent,
12
+ WAMessage,
13
+ WAMessageKey,
14
+ JIDLike,
15
+ ContextInfo
16
+ } from '../types/index.js';
17
+ import { concatBytes, getRandomBytes, sha256Hash } from '../core/crypto.js';
18
+ import { jidToString, jidUser, isGroupJid } from '../utils/jid.js';
19
+
20
+ // ============== Message ID Generator ==============
21
+
22
+ let messageCounter = 0;
23
+
24
+ /**
25
+ * Generate unique message ID
26
+ * Format: 3EB0 + 8 random hex chars + counter
27
+ * Contoh: "3EB0ABC1234567890A"
28
+ */
29
+ export function generateMessageId(): string {
30
+ messageCounter++;
31
+ const rand = getRandomBytes(8);
32
+ const randHex = Array.from(rand).map(b => b.toString(16).padStart(2, '0')).join('');
33
+ const counterHex = messageCounter.toString(16).padStart(2, '0');
34
+ return `3EB0${randHex}${counterHex}`.toUpperCase();
35
+ }
36
+
37
+ /**
38
+ * Generate stanza ID untuk IQ requests
39
+ */
40
+ export function generateStanzaId(): string {
41
+ const rand = getRandomBytes(4);
42
+ const randHex = Array.from(rand).map(b => b.toString(16).padStart(2, '0')).join('');
43
+ return `${Date.now().toString(36)}-${randHex}`;
44
+ }
45
+
46
+ // ============== Message Encoder ==============
47
+
48
+ /**
49
+ * Encode MessageContent ke WA binary node
50
+ *
51
+ * WhatsApp message structure:
52
+ * <message to="jid" type="text" id="msgId">
53
+ * <conversation>text content</conversation>
54
+ * </message>
55
+ *
56
+ * Atau untuk media:
57
+ * <message to="jid" type="media" id="msgId">
58
+ * <imageMessage>...</imageMessage>
59
+ * </message>
60
+ */
61
+ export function encodeMessageNode(
62
+ jid: JIDLike,
63
+ content: MessageContent,
64
+ options: EncodeMessageOptions = {}
65
+ ): any {
66
+ const id = options.messageId ?? generateMessageId();
67
+ const to = jidToString(jid);
68
+ const type = isGroupJid(jid) ? 'text' : 'text';
69
+
70
+ const messageNode: any = {
71
+ tag: 'message',
72
+ attrs: {
73
+ to,
74
+ type,
75
+ id
76
+ },
77
+ content: [] as any[]
78
+ };
79
+
80
+ // Add participant untuk group messages
81
+ if (options.participant) {
82
+ messageNode.attrs.participant = options.participant;
83
+ }
84
+
85
+ // Determine content type
86
+ if (content.text) {
87
+ messageNode.content.push({
88
+ tag: 'conversation',
89
+ attrs: {},
90
+ content: content.text
91
+ });
92
+ }
93
+
94
+ if (content.image) {
95
+ messageNode.content.push({
96
+ tag: 'imageMessage',
97
+ attrs: {
98
+ url: content.image.stream ? '' : '',
99
+ mimetype: content.image.mimetype,
100
+ fileSha256: '', // computed on upload
101
+ fileLength: (content.image.fileLength ?? 0).toString()
102
+ },
103
+ content: content.caption ? [{ tag: 'caption', attrs: {}, content: content.caption }] : undefined
104
+ });
105
+ }
106
+
107
+ if (content.video) {
108
+ messageNode.content.push({
109
+ tag: 'videoMessage',
110
+ attrs: {
111
+ mimetype: content.video.mimetype,
112
+ seconds: (content.video.seconds ?? 0).toString(),
113
+ gifPlayback: content.video.gifPlayback ? 'true' : 'false'
114
+ },
115
+ content: content.caption ? [{ tag: 'caption', attrs: {}, content: content.caption }] : undefined
116
+ });
117
+ }
118
+
119
+ if (content.audio) {
120
+ messageNode.content.push({
121
+ tag: 'audioMessage',
122
+ attrs: {
123
+ mimetype: content.audio.mimetype,
124
+ ptt: content.audio.ptt ? 'true' : 'false'
125
+ }
126
+ });
127
+ }
128
+
129
+ if (content.document) {
130
+ messageNode.content.push({
131
+ tag: 'documentMessage',
132
+ attrs: {
133
+ mimetype: content.document.mimetype,
134
+ fileName: content.document.fileName ?? 'document',
135
+ pageCount: content.document.pageCount?.toString() ?? '1'
136
+ }
137
+ });
138
+ }
139
+
140
+ if (content.sticker) {
141
+ messageNode.content.push({
142
+ tag: 'stickerMessage',
143
+ attrs: {
144
+ mimetype: content.sticker.mimetype,
145
+ isAnimated: content.sticker.isAnimated ? 'true' : 'false'
146
+ }
147
+ });
148
+ }
149
+
150
+ if (content.location) {
151
+ messageNode.content.push({
152
+ tag: 'locationMessage',
153
+ attrs: {
154
+ degreesLatitude: content.location.degreesLatitude.toString(),
155
+ degreesLongitude: content.location.degreesLongitude.toString(),
156
+ name: content.location.name ?? '',
157
+ address: content.location.address ?? ''
158
+ }
159
+ });
160
+ }
161
+
162
+ if (content.contact) {
163
+ messageNode.content.push({
164
+ tag: 'contactMessage',
165
+ attrs: {
166
+ displayName: content.contact.displayName
167
+ },
168
+ content: content.contact.vcard
169
+ });
170
+ }
171
+
172
+ if (content.poll) {
173
+ messageNode.content.push({
174
+ tag: 'pollCreationMessage',
175
+ attrs: {
176
+ name: content.poll.name
177
+ },
178
+ content: content.poll.values.map(v => ({
179
+ tag: 'option',
180
+ attrs: { name: v }
181
+ }))
182
+ });
183
+ }
184
+
185
+ // Quoted message (reply)
186
+ if (content.quoted) {
187
+ const contextInfo = buildContextInfo(content.quoted, content.mentions);
188
+ messageNode.content.push({
189
+ tag: 'contextInfo',
190
+ attrs: {},
191
+ content: contextInfo
192
+ });
193
+ }
194
+
195
+ // Mentions
196
+ if (content.mentions && content.mentions.length > 0 && !content.quoted) {
197
+ messageNode.content.push({
198
+ tag: 'mentionedJidList',
199
+ attrs: {},
200
+ content: content.mentions.map(m => jidToString(m))
201
+ });
202
+ }
203
+
204
+ // Ephemeral
205
+ if (content.ephemeralExpiration) {
206
+ messageNode.attrs.ephemeral = content.ephemeralExpiration.toString();
207
+ }
208
+
209
+ // View once
210
+ if (content.viewOnce) {
211
+ messageNode.attrs.viewOnce = 'true';
212
+ }
213
+
214
+ return messageNode;
215
+ }
216
+
217
+ interface EncodeMessageOptions {
218
+ messageId?: string;
219
+ participant?: string;
220
+ }
221
+
222
+ /**
223
+ * Build context info untuk quoted/reply messages
224
+ */
225
+ function buildContextInfo(quoted: WAMessage, mentions?: JIDLike[]): any[] {
226
+ const parts: any[] = [];
227
+ parts.push({
228
+ tag: 'stanzaId',
229
+ attrs: {},
230
+ content: quoted.key.id
231
+ });
232
+ parts.push({
233
+ tag: 'participant',
234
+ attrs: {},
235
+ content: jidToString(quoted.key.participant ?? quoted.key.remoteJid)
236
+ });
237
+ if (mentions && mentions.length > 0) {
238
+ parts.push({
239
+ tag: 'mentionedJidList',
240
+ attrs: {},
241
+ content: mentions.map(m => jidToString(m))
242
+ });
243
+ }
244
+ // Quoted message content (partial)
245
+ if (quoted.message?.text) {
246
+ parts.push({
247
+ tag: 'quotedMessage',
248
+ attrs: {},
249
+ content: quoted.message.text
250
+ });
251
+ }
252
+ return parts;
253
+ }
254
+
255
+ // ============== Message Decoder ==============
256
+
257
+ /**
258
+ * Decode incoming WA binary node ke WAMessage
259
+ */
260
+ export function decodeMessageNode(node: any, fromMe: boolean = false): WAMessage | null {
261
+ if (!node || node.tag !== 'message') {
262
+ return null;
263
+ }
264
+
265
+ const attrs = node.attrs || {};
266
+ const jid = attrs.from ?? attrs.to ?? '';
267
+ const id = attrs.id;
268
+ const participant = attrs.participant;
269
+
270
+ const key: WAMessageKey = {
271
+ remoteJid: jid,
272
+ fromMe,
273
+ id,
274
+ participant
275
+ };
276
+
277
+ const message: MessageContent & { [k: string]: any } = {} as any;
278
+ let messageTimestamp = Date.now();
279
+ let pushName: string | undefined;
280
+
281
+ // Process children
282
+ if (Array.isArray(node.content)) {
283
+ for (const child of node.content) {
284
+ if (typeof child === 'string') continue;
285
+ if (!child.tag) continue;
286
+
287
+ switch (child.tag) {
288
+ case 'conversation':
289
+ message.text = typeof child.content === 'string' ? child.content : '';
290
+ break;
291
+
292
+ case 'imageMessage':
293
+ message.image = {
294
+ stream: undefined as any, // akan di-populate oleh media download
295
+ mimetype: child.attrs?.mimetype ?? 'image/jpeg',
296
+ fileLength: parseInt(child.attrs?.fileLength ?? '0', 10)
297
+ };
298
+ if (child.content && Array.isArray(child.content)) {
299
+ const captionNode = child.content.find((c: any) => c.tag === 'caption');
300
+ if (captionNode) message.caption = captionNode.content;
301
+ }
302
+ break;
303
+
304
+ case 'videoMessage':
305
+ message.video = {
306
+ stream: undefined as any,
307
+ mimetype: child.attrs?.mimetype ?? 'video/mp4',
308
+ seconds: parseInt(child.attrs?.seconds ?? '0', 10),
309
+ gifPlayback: child.attrs?.gifPlayback === 'true'
310
+ };
311
+ if (child.content && Array.isArray(child.content)) {
312
+ const captionNode = child.content.find((c: any) => c.tag === 'caption');
313
+ if (captionNode) message.caption = captionNode.content;
314
+ }
315
+ break;
316
+
317
+ case 'audioMessage':
318
+ message.audio = {
319
+ stream: undefined as any,
320
+ mimetype: child.attrs?.mimetype ?? 'audio/mpeg',
321
+ seconds: parseInt(child.attrs?.seconds ?? '0', 10),
322
+ ptt: child.attrs?.ptt === 'true'
323
+ };
324
+ break;
325
+
326
+ case 'documentMessage':
327
+ message.document = {
328
+ stream: undefined as any,
329
+ mimetype: child.attrs?.mimetype ?? 'application/octet-stream',
330
+ fileName: child.attrs?.fileName ?? 'document',
331
+ pageCount: parseInt(child.attrs?.pageCount ?? '1', 10)
332
+ };
333
+ break;
334
+
335
+ case 'stickerMessage':
336
+ message.sticker = {
337
+ stream: undefined as any,
338
+ mimetype: child.attrs?.mimetype ?? 'image/webp',
339
+ isAnimated: child.attrs?.isAnimated === 'true'
340
+ };
341
+ break;
342
+
343
+ case 'locationMessage':
344
+ message.location = {
345
+ degreesLatitude: parseFloat(child.attrs?.degreesLatitude ?? '0'),
346
+ degreesLongitude: parseFloat(child.attrs?.degreesLongitude ?? '0'),
347
+ name: child.attrs?.name,
348
+ address: child.attrs?.address
349
+ };
350
+ break;
351
+
352
+ case 'contactMessage':
353
+ message.contact = {
354
+ displayName: child.attrs?.displayName ?? '',
355
+ vcard: typeof child.content === 'string' ? child.content : ''
356
+ };
357
+ break;
358
+
359
+ case 'pollCreationMessage':
360
+ message.poll = {
361
+ name: child.attrs?.name ?? '',
362
+ values: Array.isArray(child.content)
363
+ ? child.content.filter((c: any) => c.tag === 'option').map((c: any) => c.attrs?.name)
364
+ : []
365
+ };
366
+ break;
367
+
368
+ case 'contextInfo':
369
+ // Process quoted message
370
+ if (Array.isArray(child.content)) {
371
+ const stanzaId = child.content.find((c: any) => c.tag === 'stanzaId');
372
+ const participantNode = child.content.find((c: any) => c.tag === 'participant');
373
+ const mentionedJids = child.content.find((c: any) => c.tag === 'mentionedJidList');
374
+ // Build quoted message stub
375
+ if (stanzaId) {
376
+ message.quoted = {
377
+ key: {
378
+ remoteJid: jid,
379
+ fromMe: false,
380
+ id: stanzaId.content
381
+ },
382
+ message: {},
383
+ messageTimestamp: 0,
384
+ status: 'read' as const
385
+ } as WAMessage;
386
+ if (participantNode) {
387
+ message.quoted!.key.participant = participantNode.content;
388
+ }
389
+ }
390
+ if (mentionedJids && Array.isArray(mentionedJids.content)) {
391
+ message.mentions = mentionedJids.content;
392
+ }
393
+ }
394
+ break;
395
+
396
+ case 'pushName':
397
+ pushName = typeof child.content === 'string' ? child.content : undefined;
398
+ break;
399
+
400
+ case 'deviceSentTime':
401
+ case 'timestamp':
402
+ const ts = child.attrs?.value ?? child.content;
403
+ if (ts) messageTimestamp = parseInt(String(ts), 10);
404
+ break;
405
+ }
406
+ }
407
+ }
408
+
409
+ // Determine status from attrs
410
+ const status = attrs.type === 'media' ? 'sent' : 'pending';
411
+
412
+ return {
413
+ key,
414
+ message,
415
+ messageTimestamp,
416
+ pushName,
417
+ status: status as any
418
+ };
419
+ }
420
+
421
+ // ============== Receipt Encoder ==============
422
+
423
+ export type ReceiptType = 'read' | 'receipt' | 'played' | 'inactive';
424
+
425
+ /**
426
+ * Build receipt node untuk send read/delivered confirmation
427
+ */
428
+ export function buildReceiptNode(
429
+ jid: JIDLike,
430
+ messageIds: string[],
431
+ type: ReceiptType = 'read',
432
+ participant?: JIDLike
433
+ ): any {
434
+ return {
435
+ tag: 'receipt',
436
+ attrs: {
437
+ id: messageIds[0],
438
+ type,
439
+ to: jidToString(jid),
440
+ t: Math.floor(Date.now() / 1000).toString()
441
+ },
442
+ content: messageIds.map(id => ({
443
+ tag: 'receipt',
444
+ attrs: { id }
445
+ }))
446
+ };
447
+ }
448
+
449
+ // ============== Status Encoder ==============
450
+
451
+ export type MessageStatusUpdateType = 'read' | 'delivered' | 'played';
452
+
453
+ /**
454
+ * Build status node untuk update message status (read receipts)
455
+ */
456
+ export function buildStatusNode(
457
+ jid: JIDLike,
458
+ messageId: string,
459
+ type: MessageStatusUpdateType,
460
+ participant?: JIDLike
461
+ ): any {
462
+ const attrs: any = {
463
+ type,
464
+ to: jidToString(jid),
465
+ id: messageId
466
+ };
467
+ if (participant) attrs.participant = jidToString(participant);
468
+ return {
469
+ tag: 'status',
470
+ attrs,
471
+ content: undefined
472
+ };
473
+ }
474
+
475
+ // ============== Helpers ==============
476
+
477
+ /**
478
+ * Extract text content dari message
479
+ */
480
+ export function extractMessageText(message: any): string {
481
+ if (!message) return '';
482
+ if (typeof message.text === 'string') return message.text;
483
+ if (typeof message.conversation === 'string') return message.conversation;
484
+ if (message.caption) return message.caption;
485
+ return '';
486
+ }
487
+
488
+ /**
489
+ * Check apakah message punya media
490
+ */
491
+ export function hasMedia(message: any): boolean {
492
+ return !!(message?.image || message?.video || message?.audio || message?.document || message?.sticker);
493
+ }
494
+
495
+ /**
496
+ * Get media info dari message
497
+ */
498
+ export function getMediaInfo(message: any): {
499
+ type: 'image' | 'video' | 'audio' | 'document' | 'sticker';
500
+ mimetype: string;
501
+ fileName?: string;
502
+ fileLength?: number;
503
+ } | null {
504
+ if (message?.image) {
505
+ return { type: 'image', mimetype: message.image.mimetype, fileLength: message.image.fileLength };
506
+ }
507
+ if (message?.video) {
508
+ return { type: 'video', mimetype: message.video.mimetype, fileLength: message.video.fileLength };
509
+ }
510
+ if (message?.audio) {
511
+ return { type: 'audio', mimetype: message.audio.mimetype, fileLength: message.audio.fileLength };
512
+ }
513
+ if (message?.document) {
514
+ return { type: 'document', mimetype: message.document.mimetype, fileName: message.document.fileName };
515
+ }
516
+ if (message?.sticker) {
517
+ return { type: 'sticker', mimetype: message.sticker.mimetype };
518
+ }
519
+ return null;
520
+ }