miaw-core 0.9.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.
@@ -0,0 +1,2637 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.MiawClient = void 0;
40
+ const baileys_1 = __importStar(require("@whiskeysockets/baileys"));
41
+ const events_1 = require("events");
42
+ const pino_1 = __importDefault(require("pino"));
43
+ const path = __importStar(require("path"));
44
+ const AuthHandler_1 = require("../handlers/AuthHandler");
45
+ const MessageHandler_1 = require("../handlers/MessageHandler");
46
+ /**
47
+ * Main client class for interacting with WhatsApp
48
+ */
49
+ class MiawClient extends events_1.EventEmitter {
50
+ constructor(options) {
51
+ super();
52
+ this.socket = null;
53
+ this.connectionState = 'disconnected';
54
+ this.reconnectAttempts = 0;
55
+ this.reconnectTimer = null;
56
+ this.lidToJidMap = new Map();
57
+ // Set default options
58
+ this.options = {
59
+ instanceId: options.instanceId,
60
+ sessionPath: options.sessionPath || './sessions',
61
+ debug: options.debug || false,
62
+ logger: options.logger,
63
+ autoReconnect: options.autoReconnect !== false,
64
+ maxReconnectAttempts: options.maxReconnectAttempts || Infinity,
65
+ reconnectDelay: options.reconnectDelay || 3000,
66
+ };
67
+ // Initialize logger
68
+ this.logger = this.options.logger || (0, pino_1.default)({
69
+ level: this.options.debug ? 'debug' : 'silent'
70
+ });
71
+ // Initialize handlers
72
+ this.authHandler = new AuthHandler_1.AuthHandler(this.options.sessionPath, this.options.instanceId);
73
+ }
74
+ /**
75
+ * Connect to WhatsApp
76
+ */
77
+ async connect() {
78
+ try {
79
+ this.updateConnectionState('connecting');
80
+ // Load auth state
81
+ const { state, saveCreds } = await this.authHandler.initialize();
82
+ // Get latest Baileys version
83
+ const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
84
+ // Create socket
85
+ this.socket = (0, baileys_1.default)({
86
+ version,
87
+ logger: this.logger,
88
+ printQRInTerminal: false,
89
+ auth: {
90
+ creds: state.creds,
91
+ keys: (0, baileys_1.makeCacheableSignalKeyStore)(state.keys, this.logger),
92
+ },
93
+ browser: ['Miaw', 'Chrome', '1.0.0'],
94
+ generateHighQualityLinkPreview: true,
95
+ });
96
+ // Register event handlers
97
+ this.registerSocketEvents(saveCreds);
98
+ this.logger.info('Connection initiated');
99
+ }
100
+ catch (error) {
101
+ this.logger.error('Failed to connect:', error);
102
+ this.emit('error', error);
103
+ if (this.options.autoReconnect) {
104
+ this.scheduleReconnect();
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * Register socket event handlers
110
+ */
111
+ registerSocketEvents(saveCreds) {
112
+ if (!this.socket)
113
+ return;
114
+ // Connection updates
115
+ this.socket.ev.on('connection.update', async (update) => {
116
+ const { connection, lastDisconnect, qr } = update;
117
+ // Handle QR code
118
+ if (qr) {
119
+ this.updateConnectionState('qr_required');
120
+ this.emit('qr', qr);
121
+ }
122
+ // Handle connection state
123
+ if (connection === 'close') {
124
+ const shouldReconnect = this.handleDisconnect(lastDisconnect);
125
+ if (shouldReconnect && this.options.autoReconnect) {
126
+ this.scheduleReconnect();
127
+ }
128
+ }
129
+ else if (connection === 'open') {
130
+ this.reconnectAttempts = 0;
131
+ this.updateConnectionState('connected');
132
+ this.emit('ready');
133
+ this.logger.info('Connected to WhatsApp');
134
+ }
135
+ });
136
+ // Credentials update
137
+ this.socket.ev.on('creds.update', async () => {
138
+ await saveCreds();
139
+ this.emit('session_saved');
140
+ });
141
+ // Contact updates - build LID to JID mapping
142
+ this.socket.ev.on('contacts.upsert', (contacts) => {
143
+ this.updateLidToJidMapping(contacts);
144
+ });
145
+ this.socket.ev.on('contacts.update', (contacts) => {
146
+ this.updateLidToJidMapping(contacts);
147
+ });
148
+ // Chat updates - extract LID mappings from chat data
149
+ this.socket.ev.on('chats.upsert', (chats) => {
150
+ this.updateLidFromChats(chats);
151
+ });
152
+ this.socket.ev.on('chats.update', (chats) => {
153
+ this.updateLidFromChats(chats);
154
+ });
155
+ // Messages
156
+ this.socket.ev.on('messages.upsert', async (m) => {
157
+ if (m.type !== 'notify')
158
+ return;
159
+ for (const msg of m.messages) {
160
+ // Debug: Log raw Baileys message structure
161
+ if (this.options.debug) {
162
+ console.log('\n========== RAW BAILEYS MESSAGE ==========');
163
+ console.log(JSON.stringify(msg, null, 2));
164
+ console.log('=========================================\n');
165
+ }
166
+ // Check for protocol messages (edits and deletes)
167
+ const protocolMessage = msg.message?.protocolMessage;
168
+ if (protocolMessage) {
169
+ this.handleProtocolMessage(msg, protocolMessage);
170
+ continue; // Don't process as regular message
171
+ }
172
+ // Build LID to JID mapping from incoming messages
173
+ // Incoming messages have remoteJid (phone) and senderLid (LID)
174
+ if (!msg.key.fromMe && msg.key.senderLid && msg.key.remoteJid) {
175
+ const lid = msg.key.senderLid.endsWith('@lid')
176
+ ? msg.key.senderLid
177
+ : `${msg.key.senderLid}@lid`;
178
+ this.lidToJidMap.set(lid, msg.key.remoteJid);
179
+ if (this.options.debug) {
180
+ console.log(`[LID Mapping] ${lid} -> ${msg.key.remoteJid}`);
181
+ }
182
+ }
183
+ const normalized = MessageHandler_1.MessageHandler.normalize({ messages: [msg] });
184
+ if (normalized) {
185
+ this.emit('message', normalized);
186
+ }
187
+ }
188
+ });
189
+ // Reactions
190
+ this.socket.ev.on('messages.reaction', (reactions) => {
191
+ for (const { key, reaction } of reactions) {
192
+ const reactionData = {
193
+ messageId: key.id || '',
194
+ chatId: key.remoteJid || '',
195
+ reactorId: reaction.key?.participant || reaction.key?.remoteJid || '',
196
+ emoji: reaction.text || '',
197
+ isRemoval: !reaction.text,
198
+ raw: { key, reaction },
199
+ };
200
+ if (this.options.debug) {
201
+ console.log('\n========== REACTION ==========');
202
+ console.log(JSON.stringify(reactionData, null, 2));
203
+ console.log('==============================\n');
204
+ }
205
+ this.emit('message_reaction', reactionData);
206
+ }
207
+ });
208
+ // Presence updates
209
+ this.socket.ev.on('presence.update', (presence) => {
210
+ const jid = presence.id;
211
+ const presences = presence.presences;
212
+ for (const [participantJid, presenceData] of Object.entries(presences)) {
213
+ const update = {
214
+ jid: participantJid || jid,
215
+ status: presenceData.lastKnownPresence,
216
+ lastSeen: presenceData.lastSeen,
217
+ };
218
+ if (this.options.debug) {
219
+ console.log('\n========== PRESENCE UPDATE ==========');
220
+ console.log(JSON.stringify(update, null, 2));
221
+ console.log('=====================================\n');
222
+ }
223
+ this.emit('presence', update);
224
+ }
225
+ });
226
+ }
227
+ /**
228
+ * Handle protocol messages (edits, deletes)
229
+ */
230
+ handleProtocolMessage(msg, protocolMessage) {
231
+ const type = protocolMessage.type;
232
+ const key = protocolMessage.key;
233
+ // Type 0 = REVOKE (delete)
234
+ if (type === 0 && key) {
235
+ const deletion = {
236
+ messageId: key.id || '',
237
+ chatId: key.remoteJid || msg.key.remoteJid || '',
238
+ fromMe: key.fromMe || false,
239
+ participant: key.participant,
240
+ raw: msg,
241
+ };
242
+ if (this.options.debug) {
243
+ console.log('\n========== MESSAGE DELETED ==========');
244
+ console.log(JSON.stringify(deletion, null, 2));
245
+ console.log('=====================================\n');
246
+ }
247
+ this.emit('message_delete', deletion);
248
+ }
249
+ // Type 14 = MESSAGE_EDIT
250
+ if (type === 14 && protocolMessage.editedMessage) {
251
+ const editedMessage = protocolMessage.editedMessage;
252
+ const newText = editedMessage.conversation ||
253
+ editedMessage.extendedTextMessage?.text ||
254
+ editedMessage.imageMessage?.caption ||
255
+ editedMessage.videoMessage?.caption;
256
+ const edit = {
257
+ messageId: key?.id || '',
258
+ chatId: key?.remoteJid || msg.key.remoteJid || '',
259
+ newText,
260
+ editTimestamp: protocolMessage.timestampMs
261
+ ? Number(protocolMessage.timestampMs)
262
+ : Date.now(),
263
+ raw: msg,
264
+ };
265
+ if (this.options.debug) {
266
+ console.log('\n========== MESSAGE EDITED ==========');
267
+ console.log(JSON.stringify(edit, null, 2));
268
+ console.log('====================================\n');
269
+ }
270
+ this.emit('message_edit', edit);
271
+ }
272
+ }
273
+ /**
274
+ * Update LID to JID mapping from contacts
275
+ */
276
+ updateLidToJidMapping(contacts) {
277
+ for (const contact of contacts) {
278
+ // Contact has both lid and jid - create mapping
279
+ if (contact.lid && contact.id && !contact.id.endsWith('@lid')) {
280
+ this.lidToJidMap.set(contact.lid, contact.id);
281
+ if (this.options.debug) {
282
+ console.log(`[Contact LID Mapping] ${contact.lid} -> ${contact.id}`);
283
+ }
284
+ }
285
+ // Also check if id is the LID and jid field exists
286
+ if (contact.id?.endsWith('@lid') && contact.jid) {
287
+ this.lidToJidMap.set(contact.id, contact.jid);
288
+ if (this.options.debug) {
289
+ console.log(`[Contact LID Mapping] ${contact.id} -> ${contact.jid}`);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ /**
295
+ * Update LID to JID mapping from chat data
296
+ */
297
+ updateLidFromChats(chats) {
298
+ for (const chat of chats) {
299
+ // Chat may have both id (could be phone or LID) and lidJid
300
+ if (chat.lidJid && chat.id && !chat.id.endsWith('@lid')) {
301
+ const lid = chat.lidJid.endsWith('@lid')
302
+ ? chat.lidJid
303
+ : `${chat.lidJid}@lid`;
304
+ this.lidToJidMap.set(lid, chat.id);
305
+ if (this.options.debug) {
306
+ console.log(`[Chat LID Mapping] ${lid} -> ${chat.id}`);
307
+ }
308
+ }
309
+ // Reverse: if id is LID and we have a phone JID
310
+ if (chat.id?.endsWith('@lid') && chat.jid) {
311
+ this.lidToJidMap.set(chat.id, chat.jid);
312
+ if (this.options.debug) {
313
+ console.log(`[Chat LID Mapping] ${chat.id} -> ${chat.jid}`);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ /**
319
+ * Resolve LID to phone number JID
320
+ * @param lid - The LID format JID (e.g., '12345@lid')
321
+ * @returns The phone number JID if found, or the original LID
322
+ */
323
+ resolveLidToJid(lid) {
324
+ if (!lid?.endsWith('@lid')) {
325
+ return lid;
326
+ }
327
+ return this.lidToJidMap.get(lid) || lid;
328
+ }
329
+ /**
330
+ * Get phone number from LID or JID
331
+ * @param jid - Any JID format
332
+ * @returns Phone number string or undefined
333
+ */
334
+ getPhoneFromJid(jid) {
335
+ const resolved = this.resolveLidToJid(jid);
336
+ if (resolved.endsWith('@s.whatsapp.net')) {
337
+ return resolved.replace('@s.whatsapp.net', '');
338
+ }
339
+ return undefined;
340
+ }
341
+ /**
342
+ * Manually register a LID to phone number mapping
343
+ * Useful for persisting mappings across sessions
344
+ * @param lid - The LID (e.g., '12345@lid' or just '12345')
345
+ * @param phoneJid - The phone JID (e.g., '6281234567890@s.whatsapp.net' or just '6281234567890')
346
+ */
347
+ registerLidMapping(lid, phoneJid) {
348
+ const normalizedLid = lid.includes('@') ? lid : `${lid}@lid`;
349
+ const normalizedJid = phoneJid.includes('@')
350
+ ? phoneJid
351
+ : `${phoneJid}@s.whatsapp.net`;
352
+ this.lidToJidMap.set(normalizedLid, normalizedJid);
353
+ }
354
+ /**
355
+ * Get all registered LID mappings
356
+ * Useful for persisting mappings to storage
357
+ */
358
+ getLidMappings() {
359
+ return new Map(this.lidToJidMap);
360
+ }
361
+ /**
362
+ * Handle disconnection
363
+ * @returns true if should reconnect
364
+ */
365
+ handleDisconnect(lastDisconnect) {
366
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
367
+ const reason = baileys_1.DisconnectReason[statusCode] || 'unknown';
368
+ this.logger.info('Disconnected:', reason, 'Code:', statusCode);
369
+ this.updateConnectionState('disconnected');
370
+ this.emit('disconnected', reason);
371
+ // Don't reconnect if logged out
372
+ if (statusCode === baileys_1.DisconnectReason.loggedOut) {
373
+ this.logger.info('Logged out, not reconnecting');
374
+ return false;
375
+ }
376
+ return true;
377
+ }
378
+ /**
379
+ * Schedule reconnection attempt
380
+ */
381
+ scheduleReconnect() {
382
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
383
+ this.logger.error('Max reconnection attempts reached');
384
+ this.emit('error', new Error('Max reconnection attempts reached'));
385
+ return;
386
+ }
387
+ if (this.reconnectTimer) {
388
+ clearTimeout(this.reconnectTimer);
389
+ }
390
+ this.reconnectAttempts++;
391
+ this.updateConnectionState('reconnecting');
392
+ this.emit('reconnecting', this.reconnectAttempts);
393
+ this.reconnectTimer = setTimeout(() => {
394
+ this.logger.info(`Reconnecting... Attempt ${this.reconnectAttempts}`);
395
+ this.connect();
396
+ }, this.options.reconnectDelay);
397
+ }
398
+ /**
399
+ * Update connection state and emit event
400
+ */
401
+ updateConnectionState(state) {
402
+ this.connectionState = state;
403
+ this.emit('connection', state);
404
+ }
405
+ /**
406
+ * Send a text message
407
+ * @param to - Recipient phone number or JID
408
+ * @param text - Text content to send
409
+ * @param options - Optional settings (quoted for reply)
410
+ */
411
+ async sendText(to, text, options) {
412
+ try {
413
+ if (!this.socket) {
414
+ throw new Error('Not connected. Call connect() first.');
415
+ }
416
+ if (this.connectionState !== 'connected') {
417
+ throw new Error(`Cannot send message. Connection state: ${this.connectionState}`);
418
+ }
419
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
420
+ // Build send options (for quoting/replying)
421
+ const sendOptions = options?.quoted?.raw ? { quoted: options.quoted.raw } : undefined;
422
+ const result = await this.socket.sendMessage(jid, { text }, sendOptions);
423
+ return {
424
+ success: true,
425
+ messageId: result?.key?.id || undefined,
426
+ };
427
+ }
428
+ catch (error) {
429
+ this.logger.error('Failed to send message:', error);
430
+ return {
431
+ success: false,
432
+ error: error.message,
433
+ };
434
+ }
435
+ }
436
+ /**
437
+ * Send an image message
438
+ * @param to - Recipient phone number or JID
439
+ * @param image - Image source (file path, URL, or Buffer)
440
+ * @param options - Optional settings (caption, viewOnce)
441
+ */
442
+ async sendImage(to, image, options) {
443
+ try {
444
+ if (!this.socket) {
445
+ throw new Error('Not connected. Call connect() first.');
446
+ }
447
+ if (this.connectionState !== 'connected') {
448
+ throw new Error(`Cannot send message. Connection state: ${this.connectionState}`);
449
+ }
450
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
451
+ // Build image message payload
452
+ const imageContent = {
453
+ image: Buffer.isBuffer(image) ? image : { url: image },
454
+ caption: options?.caption,
455
+ viewOnce: options?.viewOnce,
456
+ };
457
+ // Build send options (for quoting/replying)
458
+ const sendOptions = options?.quoted?.raw ? { quoted: options.quoted.raw } : undefined;
459
+ const result = await this.socket.sendMessage(jid, imageContent, sendOptions);
460
+ return {
461
+ success: true,
462
+ messageId: result?.key?.id || undefined,
463
+ };
464
+ }
465
+ catch (error) {
466
+ this.logger.error('Failed to send image:', error);
467
+ return {
468
+ success: false,
469
+ error: error.message,
470
+ };
471
+ }
472
+ }
473
+ /**
474
+ * Send a document message
475
+ * @param to - Recipient phone number or JID
476
+ * @param document - Document source (file path, URL, or Buffer)
477
+ * @param options - Optional settings (fileName, mimetype, caption)
478
+ */
479
+ async sendDocument(to, document, options) {
480
+ try {
481
+ if (!this.socket) {
482
+ throw new Error('Not connected. Call connect() first.');
483
+ }
484
+ if (this.connectionState !== 'connected') {
485
+ throw new Error(`Cannot send message. Connection state: ${this.connectionState}`);
486
+ }
487
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
488
+ // Determine filename and mimetype
489
+ let fileName = options?.fileName;
490
+ let mimetype = options?.mimetype;
491
+ // If document is a file path string and no fileName provided, extract from path
492
+ if (!Buffer.isBuffer(document) && !fileName) {
493
+ fileName = path.basename(document);
494
+ }
495
+ // Auto-detect mimetype from fileName if not provided
496
+ if (!mimetype && fileName) {
497
+ mimetype = this.getMimetypeFromFileName(fileName);
498
+ }
499
+ // Build document message payload
500
+ const documentContent = {
501
+ document: Buffer.isBuffer(document) ? document : { url: document },
502
+ fileName: fileName || 'document',
503
+ mimetype: mimetype || 'application/octet-stream',
504
+ caption: options?.caption,
505
+ };
506
+ // Build send options (for quoting/replying)
507
+ const sendOptions = options?.quoted?.raw ? { quoted: options.quoted.raw } : undefined;
508
+ const result = await this.socket.sendMessage(jid, documentContent, sendOptions);
509
+ return {
510
+ success: true,
511
+ messageId: result?.key?.id || undefined,
512
+ };
513
+ }
514
+ catch (error) {
515
+ this.logger.error('Failed to send document:', error);
516
+ return {
517
+ success: false,
518
+ error: error.message,
519
+ };
520
+ }
521
+ }
522
+ /**
523
+ * Send a video message
524
+ * @param to - Recipient phone number or JID
525
+ * @param video - Video source (file path, URL, or Buffer)
526
+ * @param options - Optional settings (caption, viewOnce, gifPlayback, ptv)
527
+ */
528
+ async sendVideo(to, video, options) {
529
+ try {
530
+ if (!this.socket) {
531
+ throw new Error('Not connected. Call connect() first.');
532
+ }
533
+ if (this.connectionState !== 'connected') {
534
+ throw new Error(`Cannot send message. Connection state: ${this.connectionState}`);
535
+ }
536
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
537
+ // Build video message payload
538
+ const videoContent = {
539
+ video: Buffer.isBuffer(video) ? video : { url: video },
540
+ caption: options?.caption,
541
+ viewOnce: options?.viewOnce,
542
+ gifPlayback: options?.gifPlayback,
543
+ ptv: options?.ptv,
544
+ };
545
+ // Build send options (for quoting/replying)
546
+ const sendOptions = options?.quoted?.raw ? { quoted: options.quoted.raw } : undefined;
547
+ const result = await this.socket.sendMessage(jid, videoContent, sendOptions);
548
+ return {
549
+ success: true,
550
+ messageId: result?.key?.id || undefined,
551
+ };
552
+ }
553
+ catch (error) {
554
+ this.logger.error('Failed to send video:', error);
555
+ return {
556
+ success: false,
557
+ error: error.message,
558
+ };
559
+ }
560
+ }
561
+ /**
562
+ * Send an audio message
563
+ * @param to - Recipient phone number or JID
564
+ * @param audio - Audio source (file path, URL, or Buffer)
565
+ * @param options - Optional settings (ptt for voice note, mimetype)
566
+ */
567
+ async sendAudio(to, audio, options) {
568
+ try {
569
+ if (!this.socket) {
570
+ throw new Error('Not connected. Call connect() first.');
571
+ }
572
+ if (this.connectionState !== 'connected') {
573
+ throw new Error(`Cannot send message. Connection state: ${this.connectionState}`);
574
+ }
575
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
576
+ // Determine mimetype
577
+ let mimetype = options?.mimetype;
578
+ if (!mimetype && !Buffer.isBuffer(audio)) {
579
+ mimetype = this.getAudioMimetypeFromFileName(audio);
580
+ }
581
+ // Build audio message payload
582
+ const audioContent = {
583
+ audio: Buffer.isBuffer(audio) ? audio : { url: audio },
584
+ mimetype: mimetype || 'audio/mp4',
585
+ ptt: options?.ptt,
586
+ };
587
+ // Build send options (for quoting/replying)
588
+ const sendOptions = options?.quoted?.raw ? { quoted: options.quoted.raw } : undefined;
589
+ const result = await this.socket.sendMessage(jid, audioContent, sendOptions);
590
+ return {
591
+ success: true,
592
+ messageId: result?.key?.id || undefined,
593
+ };
594
+ }
595
+ catch (error) {
596
+ this.logger.error('Failed to send audio:', error);
597
+ return {
598
+ success: false,
599
+ error: error.message,
600
+ };
601
+ }
602
+ }
603
+ /**
604
+ * Download media from a received message
605
+ * @param message - The MiawMessage containing media (must have raw field with original Baileys message)
606
+ * @returns Buffer containing the media data, or null if download fails
607
+ */
608
+ async downloadMedia(message) {
609
+ try {
610
+ if (!message.raw) {
611
+ throw new Error('Message does not contain raw Baileys data. Cannot download media.');
612
+ }
613
+ // Check if this is a media message
614
+ const mediaTypes = ['image', 'video', 'audio', 'document', 'sticker'];
615
+ if (!mediaTypes.includes(message.type)) {
616
+ throw new Error(`Message type '${message.type}' is not a downloadable media type.`);
617
+ }
618
+ const buffer = await (0, baileys_1.downloadMediaMessage)(message.raw, 'buffer', {}, {
619
+ logger: this.logger,
620
+ // reuploadRequest allows re-uploading expired media
621
+ reuploadRequest: this.socket
622
+ ? this.socket.updateMediaMessage
623
+ : async (msg) => msg,
624
+ });
625
+ return buffer;
626
+ }
627
+ catch (error) {
628
+ this.logger.error('Failed to download media:', error);
629
+ return null;
630
+ }
631
+ }
632
+ // ============================================
633
+ // Contact & Validation Methods (v0.4.0)
634
+ // ============================================
635
+ /**
636
+ * Check if a phone number is registered on WhatsApp
637
+ * @param phone - Phone number to check (with country code, e.g., '6281234567890')
638
+ * @returns CheckNumberResult with exists flag and JID if found
639
+ */
640
+ async checkNumber(phone) {
641
+ try {
642
+ if (!this.socket) {
643
+ throw new Error('Not connected. Call connect() first.');
644
+ }
645
+ if (this.connectionState !== 'connected') {
646
+ throw new Error(`Cannot check number. Connection state: ${this.connectionState}`);
647
+ }
648
+ // Clean phone number - remove non-digits
649
+ const cleanPhone = phone.replace(/\D/g, '');
650
+ const results = await this.socket.onWhatsApp(cleanPhone);
651
+ const result = results?.[0];
652
+ return {
653
+ exists: !!result?.exists,
654
+ jid: result?.jid,
655
+ };
656
+ }
657
+ catch (error) {
658
+ this.logger.error('Failed to check number:', error);
659
+ return {
660
+ exists: false,
661
+ error: error.message,
662
+ };
663
+ }
664
+ }
665
+ /**
666
+ * Check multiple phone numbers on WhatsApp (batch check)
667
+ * @param phones - Array of phone numbers to check
668
+ * @returns Array of CheckNumberResult
669
+ */
670
+ async checkNumbers(phones) {
671
+ try {
672
+ if (!this.socket) {
673
+ throw new Error('Not connected. Call connect() first.');
674
+ }
675
+ if (this.connectionState !== 'connected') {
676
+ throw new Error(`Cannot check numbers. Connection state: ${this.connectionState}`);
677
+ }
678
+ // Clean all phone numbers
679
+ const cleanPhones = phones.map((p) => p.replace(/\D/g, ''));
680
+ const results = await this.socket.onWhatsApp(...cleanPhones);
681
+ return (results || []).map((result) => ({
682
+ exists: !!result?.exists,
683
+ jid: result?.jid,
684
+ }));
685
+ }
686
+ catch (error) {
687
+ this.logger.error('Failed to check numbers:', error);
688
+ return phones.map(() => ({
689
+ exists: false,
690
+ error: error.message,
691
+ }));
692
+ }
693
+ }
694
+ /**
695
+ * Get contact information including status
696
+ * @param jidOrPhone - Contact's JID or phone number
697
+ * @returns ContactInfo or null if not found
698
+ */
699
+ async getContactInfo(jidOrPhone) {
700
+ try {
701
+ if (!this.socket) {
702
+ throw new Error('Not connected. Call connect() first.');
703
+ }
704
+ if (this.connectionState !== 'connected') {
705
+ throw new Error(`Cannot get contact info. Connection state: ${this.connectionState}`);
706
+ }
707
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(jidOrPhone);
708
+ // Fetch status
709
+ let status;
710
+ try {
711
+ const statusResult = await this.socket.fetchStatus(jid);
712
+ // fetchStatus returns an array, get first result
713
+ const firstResult = Array.isArray(statusResult) ? statusResult[0] : statusResult;
714
+ status = firstResult?.status?.status || undefined;
715
+ }
716
+ catch {
717
+ // Status might not be available
718
+ }
719
+ // Check if business account
720
+ let isBusiness = false;
721
+ try {
722
+ const businessProfile = await this.socket.getBusinessProfile(jid);
723
+ isBusiness = !!businessProfile;
724
+ }
725
+ catch {
726
+ // Not a business account or not available
727
+ }
728
+ // Extract phone from JID
729
+ const phone = jid.endsWith('@s.whatsapp.net')
730
+ ? jid.replace('@s.whatsapp.net', '')
731
+ : undefined;
732
+ return {
733
+ jid,
734
+ phone,
735
+ status,
736
+ isBusiness,
737
+ };
738
+ }
739
+ catch (error) {
740
+ this.logger.error('Failed to get contact info:', error);
741
+ return null;
742
+ }
743
+ }
744
+ /**
745
+ * Get business profile information
746
+ * @param jidOrPhone - Contact's JID or phone number
747
+ * @returns BusinessProfile or null if not a business account
748
+ */
749
+ async getBusinessProfile(jidOrPhone) {
750
+ try {
751
+ if (!this.socket) {
752
+ throw new Error('Not connected. Call connect() first.');
753
+ }
754
+ if (this.connectionState !== 'connected') {
755
+ throw new Error(`Cannot get business profile. Connection state: ${this.connectionState}`);
756
+ }
757
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(jidOrPhone);
758
+ const profile = await this.socket.getBusinessProfile(jid);
759
+ if (!profile) {
760
+ return null;
761
+ }
762
+ return {
763
+ description: profile.description || undefined,
764
+ category: profile.category || undefined,
765
+ website: Array.isArray(profile.website) ? profile.website[0] : profile.website,
766
+ email: profile.email || undefined,
767
+ address: profile.address || undefined,
768
+ };
769
+ }
770
+ catch (error) {
771
+ this.logger.error('Failed to get business profile:', error);
772
+ return null;
773
+ }
774
+ }
775
+ /**
776
+ * Get profile picture URL for a contact or group
777
+ * @param jidOrPhone - Contact's JID, phone number, or group JID
778
+ * @param highRes - Whether to get high resolution image (default: false)
779
+ * @returns Profile picture URL or null if not available
780
+ */
781
+ async getProfilePicture(jidOrPhone, highRes = false) {
782
+ try {
783
+ if (!this.socket) {
784
+ throw new Error('Not connected. Call connect() first.');
785
+ }
786
+ if (this.connectionState !== 'connected') {
787
+ throw new Error(`Cannot get profile picture. Connection state: ${this.connectionState}`);
788
+ }
789
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(jidOrPhone);
790
+ const url = await this.socket.profilePictureUrl(jid, highRes ? 'image' : 'preview');
791
+ return url || null;
792
+ }
793
+ catch (error) {
794
+ // Profile picture might not be available (privacy settings)
795
+ this.logger.debug('Profile picture not available:', error);
796
+ return null;
797
+ }
798
+ }
799
+ // ============================================
800
+ // Group Methods (v0.4.0)
801
+ // ============================================
802
+ /**
803
+ * Get group metadata/information
804
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
805
+ * @returns GroupInfo or null if not found
806
+ */
807
+ async getGroupInfo(groupJid) {
808
+ try {
809
+ if (!this.socket) {
810
+ throw new Error('Not connected. Call connect() first.');
811
+ }
812
+ if (this.connectionState !== 'connected') {
813
+ throw new Error(`Cannot get group info. Connection state: ${this.connectionState}`);
814
+ }
815
+ // Ensure it's a group JID
816
+ if (!groupJid.endsWith('@g.us')) {
817
+ throw new Error('Invalid group JID. Must end with @g.us');
818
+ }
819
+ const metadata = await this.socket.groupMetadata(groupJid);
820
+ const participants = metadata.participants.map((p) => ({
821
+ jid: p.id,
822
+ role: p.admin === 'superadmin' ? 'superadmin' : p.admin === 'admin' ? 'admin' : 'member',
823
+ }));
824
+ return {
825
+ jid: metadata.id,
826
+ name: metadata.subject,
827
+ description: metadata.desc || undefined,
828
+ owner: metadata.owner || undefined,
829
+ createdAt: metadata.creation,
830
+ participantCount: metadata.participants.length,
831
+ participants,
832
+ announce: metadata.announce,
833
+ restrict: metadata.restrict,
834
+ };
835
+ }
836
+ catch (error) {
837
+ this.logger.error('Failed to get group info:', error);
838
+ return null;
839
+ }
840
+ }
841
+ /**
842
+ * Get list of participants in a group
843
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
844
+ * @returns Array of GroupParticipant or null if group not found
845
+ */
846
+ async getGroupParticipants(groupJid) {
847
+ const groupInfo = await this.getGroupInfo(groupJid);
848
+ return groupInfo?.participants || null;
849
+ }
850
+ // ============================================
851
+ // Advanced Messaging Methods (v0.6.0)
852
+ // ============================================
853
+ /**
854
+ * Send a reaction to a message
855
+ * @param message - The MiawMessage to react to (must have raw field)
856
+ * @param emoji - Emoji to react with (e.g., '❤️', '👍'). Empty string removes reaction.
857
+ * @returns SendMessageResult
858
+ */
859
+ async sendReaction(message, emoji) {
860
+ try {
861
+ if (!this.socket) {
862
+ throw new Error('Not connected. Call connect() first.');
863
+ }
864
+ if (this.connectionState !== 'connected') {
865
+ throw new Error(`Cannot send reaction. Connection state: ${this.connectionState}`);
866
+ }
867
+ if (!message.raw?.key) {
868
+ throw new Error('Message does not contain raw Baileys key data. Cannot send reaction.');
869
+ }
870
+ const jid = message.raw.key.remoteJid;
871
+ if (!jid) {
872
+ throw new Error('Message does not have a valid chat JID.');
873
+ }
874
+ const result = await this.socket.sendMessage(jid, {
875
+ react: {
876
+ text: emoji,
877
+ key: message.raw.key,
878
+ },
879
+ });
880
+ return {
881
+ success: true,
882
+ messageId: result?.key?.id || undefined,
883
+ };
884
+ }
885
+ catch (error) {
886
+ this.logger.error('Failed to send reaction:', error);
887
+ return {
888
+ success: false,
889
+ error: error.message,
890
+ };
891
+ }
892
+ }
893
+ /**
894
+ * Remove a reaction from a message (alias for sendReaction with empty string)
895
+ * @param message - The MiawMessage to remove reaction from
896
+ * @returns SendMessageResult
897
+ */
898
+ async removeReaction(message) {
899
+ return this.sendReaction(message, '');
900
+ }
901
+ /**
902
+ * Forward a message to another chat
903
+ * @param message - The MiawMessage to forward (must have raw field)
904
+ * @param to - Recipient phone number, JID, or group JID
905
+ * @returns SendMessageResult
906
+ */
907
+ async forwardMessage(message, to) {
908
+ try {
909
+ if (!this.socket) {
910
+ throw new Error('Not connected. Call connect() first.');
911
+ }
912
+ if (this.connectionState !== 'connected') {
913
+ throw new Error(`Cannot forward message. Connection state: ${this.connectionState}`);
914
+ }
915
+ if (!message.raw) {
916
+ throw new Error('Message does not contain raw Baileys data. Cannot forward.');
917
+ }
918
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
919
+ const result = await this.socket.sendMessage(jid, {
920
+ forward: message.raw,
921
+ });
922
+ return {
923
+ success: true,
924
+ messageId: result?.key?.id || undefined,
925
+ };
926
+ }
927
+ catch (error) {
928
+ this.logger.error('Failed to forward message:', error);
929
+ return {
930
+ success: false,
931
+ error: error.message,
932
+ };
933
+ }
934
+ }
935
+ /**
936
+ * Edit a previously sent message (must be your own message, within 15 minutes)
937
+ * @param message - The MiawMessage to edit (must be fromMe and have raw field)
938
+ * @param newText - New text content for the message
939
+ * @returns SendMessageResult
940
+ */
941
+ async editMessage(message, newText) {
942
+ try {
943
+ if (!this.socket) {
944
+ throw new Error('Not connected. Call connect() first.');
945
+ }
946
+ if (this.connectionState !== 'connected') {
947
+ throw new Error(`Cannot edit message. Connection state: ${this.connectionState}`);
948
+ }
949
+ if (!message.raw?.key) {
950
+ throw new Error('Message does not contain raw Baileys key data. Cannot edit.');
951
+ }
952
+ if (!message.fromMe) {
953
+ throw new Error('Can only edit your own messages.');
954
+ }
955
+ const jid = message.raw.key.remoteJid;
956
+ if (!jid) {
957
+ throw new Error('Message does not have a valid chat JID.');
958
+ }
959
+ const result = await this.socket.sendMessage(jid, {
960
+ text: newText,
961
+ edit: message.raw.key,
962
+ });
963
+ return {
964
+ success: true,
965
+ messageId: result?.key?.id || undefined,
966
+ };
967
+ }
968
+ catch (error) {
969
+ this.logger.error('Failed to edit message:', error);
970
+ return {
971
+ success: false,
972
+ error: error.message,
973
+ };
974
+ }
975
+ }
976
+ /**
977
+ * Delete a message for everyone (must be your own message or admin in group)
978
+ * @param message - The MiawMessage to delete (must have raw field)
979
+ * @returns SendMessageResult
980
+ */
981
+ async deleteMessage(message) {
982
+ try {
983
+ if (!this.socket) {
984
+ throw new Error('Not connected. Call connect() first.');
985
+ }
986
+ if (this.connectionState !== 'connected') {
987
+ throw new Error(`Cannot delete message. Connection state: ${this.connectionState}`);
988
+ }
989
+ if (!message.raw?.key) {
990
+ throw new Error('Message does not contain raw Baileys key data. Cannot delete.');
991
+ }
992
+ const jid = message.raw.key.remoteJid;
993
+ if (!jid) {
994
+ throw new Error('Message does not have a valid chat JID.');
995
+ }
996
+ const result = await this.socket.sendMessage(jid, {
997
+ delete: message.raw.key,
998
+ });
999
+ return {
1000
+ success: true,
1001
+ messageId: result?.key?.id || undefined,
1002
+ };
1003
+ }
1004
+ catch (error) {
1005
+ this.logger.error('Failed to delete message:', error);
1006
+ return {
1007
+ success: false,
1008
+ error: error.message,
1009
+ };
1010
+ }
1011
+ }
1012
+ /**
1013
+ * Delete a message for yourself only (does not affect other participants)
1014
+ * @param message - The MiawMessage to delete locally (must have raw field)
1015
+ * @param deleteMedia - Whether to also delete associated media files (default: true)
1016
+ * @returns boolean indicating success
1017
+ */
1018
+ async deleteMessageForMe(message, deleteMedia = true) {
1019
+ try {
1020
+ if (!this.socket) {
1021
+ throw new Error('Not connected. Call connect() first.');
1022
+ }
1023
+ if (this.connectionState !== 'connected') {
1024
+ throw new Error(`Cannot delete message. Connection state: ${this.connectionState}`);
1025
+ }
1026
+ if (!message.raw?.key) {
1027
+ throw new Error('Message does not contain raw Baileys key data. Cannot delete.');
1028
+ }
1029
+ const jid = message.raw.key.remoteJid;
1030
+ if (!jid) {
1031
+ throw new Error('Message does not have a valid chat JID.');
1032
+ }
1033
+ await this.socket.chatModify({
1034
+ deleteForMe: {
1035
+ deleteMedia,
1036
+ key: message.raw.key,
1037
+ timestamp: message.timestamp,
1038
+ },
1039
+ }, jid);
1040
+ return true;
1041
+ }
1042
+ catch (error) {
1043
+ this.logger.error('Failed to delete message for me:', error);
1044
+ return false;
1045
+ }
1046
+ }
1047
+ // ============================================
1048
+ // UX Methods (v0.5.0)
1049
+ // ============================================
1050
+ /**
1051
+ * Mark a message as read (send read receipt)
1052
+ * @param message - The MiawMessage to mark as read
1053
+ * @returns true if successful, false otherwise
1054
+ */
1055
+ async markAsRead(message) {
1056
+ try {
1057
+ if (!this.socket) {
1058
+ throw new Error('Not connected. Call connect() first.');
1059
+ }
1060
+ if (this.connectionState !== 'connected') {
1061
+ throw new Error(`Cannot mark as read. Connection state: ${this.connectionState}`);
1062
+ }
1063
+ if (!message.raw?.key) {
1064
+ throw new Error('Message does not contain raw Baileys key data.');
1065
+ }
1066
+ await this.socket.readMessages([message.raw.key]);
1067
+ return true;
1068
+ }
1069
+ catch (error) {
1070
+ this.logger.error('Failed to mark as read:', error);
1071
+ return false;
1072
+ }
1073
+ }
1074
+ /**
1075
+ * Send typing indicator to a chat
1076
+ * @param to - Recipient phone number, JID, or group JID
1077
+ */
1078
+ async sendTyping(to) {
1079
+ try {
1080
+ if (!this.socket) {
1081
+ throw new Error('Not connected. Call connect() first.');
1082
+ }
1083
+ if (this.connectionState !== 'connected') {
1084
+ throw new Error(`Cannot send typing. Connection state: ${this.connectionState}`);
1085
+ }
1086
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
1087
+ await this.socket.sendPresenceUpdate('composing', jid);
1088
+ }
1089
+ catch (error) {
1090
+ this.logger.error('Failed to send typing indicator:', error);
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Send recording indicator to a chat (shows "recording audio...")
1095
+ * @param to - Recipient phone number, JID, or group JID
1096
+ */
1097
+ async sendRecording(to) {
1098
+ try {
1099
+ if (!this.socket) {
1100
+ throw new Error('Not connected. Call connect() first.');
1101
+ }
1102
+ if (this.connectionState !== 'connected') {
1103
+ throw new Error(`Cannot send recording. Connection state: ${this.connectionState}`);
1104
+ }
1105
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
1106
+ await this.socket.sendPresenceUpdate('recording', jid);
1107
+ }
1108
+ catch (error) {
1109
+ this.logger.error('Failed to send recording indicator:', error);
1110
+ }
1111
+ }
1112
+ /**
1113
+ * Stop typing/recording indicator (send paused state)
1114
+ * @param to - Recipient phone number, JID, or group JID
1115
+ */
1116
+ async stopTyping(to) {
1117
+ try {
1118
+ if (!this.socket) {
1119
+ throw new Error('Not connected. Call connect() first.');
1120
+ }
1121
+ if (this.connectionState !== 'connected') {
1122
+ throw new Error(`Cannot stop typing. Connection state: ${this.connectionState}`);
1123
+ }
1124
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(to);
1125
+ await this.socket.sendPresenceUpdate('paused', jid);
1126
+ }
1127
+ catch (error) {
1128
+ this.logger.error('Failed to stop typing indicator:', error);
1129
+ }
1130
+ }
1131
+ /**
1132
+ * Set bot's presence status (online/offline)
1133
+ * @param status - 'available' (online) or 'unavailable' (offline)
1134
+ */
1135
+ async setPresence(status) {
1136
+ try {
1137
+ if (!this.socket) {
1138
+ throw new Error('Not connected. Call connect() first.');
1139
+ }
1140
+ if (this.connectionState !== 'connected') {
1141
+ throw new Error(`Cannot set presence. Connection state: ${this.connectionState}`);
1142
+ }
1143
+ await this.socket.sendPresenceUpdate(status);
1144
+ }
1145
+ catch (error) {
1146
+ this.logger.error('Failed to set presence:', error);
1147
+ }
1148
+ }
1149
+ /**
1150
+ * Subscribe to presence updates for a contact
1151
+ * After subscribing, you'll receive 'presence' events when the contact's status changes
1152
+ * @param jidOrPhone - Contact's JID or phone number to monitor
1153
+ */
1154
+ async subscribePresence(jidOrPhone) {
1155
+ try {
1156
+ if (!this.socket) {
1157
+ throw new Error('Not connected. Call connect() first.');
1158
+ }
1159
+ if (this.connectionState !== 'connected') {
1160
+ throw new Error(`Cannot subscribe to presence. Connection state: ${this.connectionState}`);
1161
+ }
1162
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(jidOrPhone);
1163
+ await this.socket.presenceSubscribe(jid);
1164
+ }
1165
+ catch (error) {
1166
+ this.logger.error('Failed to subscribe to presence:', error);
1167
+ }
1168
+ }
1169
+ // ============================================
1170
+ // Group Management Methods (v0.7.0)
1171
+ // ============================================
1172
+ /**
1173
+ * Helper method for group participant operations
1174
+ */
1175
+ async groupParticipantsOperation(groupJid, participants, action) {
1176
+ if (!this.socket) {
1177
+ throw new Error('Not connected. Call connect() first.');
1178
+ }
1179
+ if (this.connectionState !== 'connected') {
1180
+ throw new Error(`Cannot perform group operation. Connection state: ${this.connectionState}`);
1181
+ }
1182
+ if (!groupJid.endsWith('@g.us')) {
1183
+ throw new Error('Invalid group JID. Must end with @g.us');
1184
+ }
1185
+ // Format participant JIDs
1186
+ const formattedParticipants = participants.map((p) => MessageHandler_1.MessageHandler.formatPhoneToJid(p));
1187
+ const results = await this.socket.groupParticipantsUpdate(groupJid, formattedParticipants, action);
1188
+ return results.map((result) => ({
1189
+ jid: result.jid || '',
1190
+ status: result.status,
1191
+ success: result.status === '200',
1192
+ }));
1193
+ }
1194
+ /**
1195
+ * Create a new WhatsApp group
1196
+ * @param name - Group name/subject
1197
+ * @param participants - Array of phone numbers or JIDs to add as initial members
1198
+ * @returns CreateGroupResult with group info if successful
1199
+ */
1200
+ async createGroup(name, participants) {
1201
+ try {
1202
+ if (!this.socket) {
1203
+ throw new Error('Not connected. Call connect() first.');
1204
+ }
1205
+ if (this.connectionState !== 'connected') {
1206
+ throw new Error(`Cannot create group. Connection state: ${this.connectionState}`);
1207
+ }
1208
+ // Format participant JIDs
1209
+ const formattedParticipants = participants.map((p) => MessageHandler_1.MessageHandler.formatPhoneToJid(p));
1210
+ const metadata = await this.socket.groupCreate(name, formattedParticipants);
1211
+ const groupParticipants = metadata.participants.map((p) => ({
1212
+ jid: p.id,
1213
+ role: p.admin === 'superadmin' ? 'superadmin' : p.admin === 'admin' ? 'admin' : 'member',
1214
+ }));
1215
+ return {
1216
+ success: true,
1217
+ groupJid: metadata.id,
1218
+ groupInfo: {
1219
+ jid: metadata.id,
1220
+ name: metadata.subject,
1221
+ description: metadata.desc || undefined,
1222
+ owner: metadata.owner || undefined,
1223
+ createdAt: metadata.creation,
1224
+ participantCount: metadata.participants.length,
1225
+ participants: groupParticipants,
1226
+ announce: metadata.announce,
1227
+ restrict: metadata.restrict,
1228
+ },
1229
+ };
1230
+ }
1231
+ catch (error) {
1232
+ this.logger.error('Failed to create group:', error);
1233
+ return {
1234
+ success: false,
1235
+ error: error.message,
1236
+ };
1237
+ }
1238
+ }
1239
+ /**
1240
+ * Add participants to a group
1241
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1242
+ * @param participants - Array of phone numbers or JIDs to add
1243
+ * @returns Array of ParticipantOperationResult for each participant
1244
+ */
1245
+ async addParticipants(groupJid, participants) {
1246
+ try {
1247
+ return await this.groupParticipantsOperation(groupJid, participants, 'add');
1248
+ }
1249
+ catch (error) {
1250
+ this.logger.error('Failed to add participants:', error);
1251
+ return participants.map((p) => ({
1252
+ jid: MessageHandler_1.MessageHandler.formatPhoneToJid(p),
1253
+ status: 'error',
1254
+ success: false,
1255
+ }));
1256
+ }
1257
+ }
1258
+ /**
1259
+ * Remove participants from a group
1260
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1261
+ * @param participants - Array of phone numbers or JIDs to remove
1262
+ * @returns Array of ParticipantOperationResult for each participant
1263
+ */
1264
+ async removeParticipants(groupJid, participants) {
1265
+ try {
1266
+ return await this.groupParticipantsOperation(groupJid, participants, 'remove');
1267
+ }
1268
+ catch (error) {
1269
+ this.logger.error('Failed to remove participants:', error);
1270
+ return participants.map((p) => ({
1271
+ jid: MessageHandler_1.MessageHandler.formatPhoneToJid(p),
1272
+ status: 'error',
1273
+ success: false,
1274
+ }));
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Leave a group
1279
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1280
+ * @returns GroupOperationResult
1281
+ */
1282
+ async leaveGroup(groupJid) {
1283
+ try {
1284
+ if (!this.socket) {
1285
+ throw new Error('Not connected. Call connect() first.');
1286
+ }
1287
+ if (this.connectionState !== 'connected') {
1288
+ throw new Error(`Cannot leave group. Connection state: ${this.connectionState}`);
1289
+ }
1290
+ if (!groupJid.endsWith('@g.us')) {
1291
+ throw new Error('Invalid group JID. Must end with @g.us');
1292
+ }
1293
+ await this.socket.groupLeave(groupJid);
1294
+ return { success: true };
1295
+ }
1296
+ catch (error) {
1297
+ this.logger.error('Failed to leave group:', error);
1298
+ return {
1299
+ success: false,
1300
+ error: error.message,
1301
+ };
1302
+ }
1303
+ }
1304
+ /**
1305
+ * Promote participants to group admin
1306
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1307
+ * @param participants - Array of phone numbers or JIDs to promote
1308
+ * @returns Array of ParticipantOperationResult for each participant
1309
+ */
1310
+ async promoteToAdmin(groupJid, participants) {
1311
+ try {
1312
+ return await this.groupParticipantsOperation(groupJid, participants, 'promote');
1313
+ }
1314
+ catch (error) {
1315
+ this.logger.error('Failed to promote participants:', error);
1316
+ return participants.map((p) => ({
1317
+ jid: MessageHandler_1.MessageHandler.formatPhoneToJid(p),
1318
+ status: 'error',
1319
+ success: false,
1320
+ }));
1321
+ }
1322
+ }
1323
+ /**
1324
+ * Demote admins to regular members
1325
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1326
+ * @param participants - Array of phone numbers or JIDs to demote
1327
+ * @returns Array of ParticipantOperationResult for each participant
1328
+ */
1329
+ async demoteFromAdmin(groupJid, participants) {
1330
+ try {
1331
+ return await this.groupParticipantsOperation(groupJid, participants, 'demote');
1332
+ }
1333
+ catch (error) {
1334
+ this.logger.error('Failed to demote participants:', error);
1335
+ return participants.map((p) => ({
1336
+ jid: MessageHandler_1.MessageHandler.formatPhoneToJid(p),
1337
+ status: 'error',
1338
+ success: false,
1339
+ }));
1340
+ }
1341
+ }
1342
+ /**
1343
+ * Update group name/subject
1344
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1345
+ * @param name - New group name
1346
+ * @returns GroupOperationResult
1347
+ */
1348
+ async updateGroupName(groupJid, name) {
1349
+ try {
1350
+ if (!this.socket) {
1351
+ throw new Error('Not connected. Call connect() first.');
1352
+ }
1353
+ if (this.connectionState !== 'connected') {
1354
+ throw new Error(`Cannot update group name. Connection state: ${this.connectionState}`);
1355
+ }
1356
+ if (!groupJid.endsWith('@g.us')) {
1357
+ throw new Error('Invalid group JID. Must end with @g.us');
1358
+ }
1359
+ await this.socket.groupUpdateSubject(groupJid, name);
1360
+ return { success: true };
1361
+ }
1362
+ catch (error) {
1363
+ this.logger.error('Failed to update group name:', error);
1364
+ return {
1365
+ success: false,
1366
+ error: error.message,
1367
+ };
1368
+ }
1369
+ }
1370
+ /**
1371
+ * Update group description
1372
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1373
+ * @param description - New description (undefined to remove)
1374
+ * @returns GroupOperationResult
1375
+ */
1376
+ async updateGroupDescription(groupJid, description) {
1377
+ try {
1378
+ if (!this.socket) {
1379
+ throw new Error('Not connected. Call connect() first.');
1380
+ }
1381
+ if (this.connectionState !== 'connected') {
1382
+ throw new Error(`Cannot update group description. Connection state: ${this.connectionState}`);
1383
+ }
1384
+ if (!groupJid.endsWith('@g.us')) {
1385
+ throw new Error('Invalid group JID. Must end with @g.us');
1386
+ }
1387
+ await this.socket.groupUpdateDescription(groupJid, description);
1388
+ return { success: true };
1389
+ }
1390
+ catch (error) {
1391
+ this.logger.error('Failed to update group description:', error);
1392
+ return {
1393
+ success: false,
1394
+ error: error.message,
1395
+ };
1396
+ }
1397
+ }
1398
+ /**
1399
+ * Update group profile picture
1400
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1401
+ * @param image - Image source (file path, URL, or Buffer)
1402
+ * @returns GroupOperationResult
1403
+ */
1404
+ async updateGroupPicture(groupJid, image) {
1405
+ try {
1406
+ if (!this.socket) {
1407
+ throw new Error('Not connected. Call connect() first.');
1408
+ }
1409
+ if (this.connectionState !== 'connected') {
1410
+ throw new Error(`Cannot update group picture. Connection state: ${this.connectionState}`);
1411
+ }
1412
+ if (!groupJid.endsWith('@g.us')) {
1413
+ throw new Error('Invalid group JID. Must end with @g.us');
1414
+ }
1415
+ const imageContent = Buffer.isBuffer(image) ? image : { url: image };
1416
+ await this.socket.updateProfilePicture(groupJid, imageContent);
1417
+ return { success: true };
1418
+ }
1419
+ catch (error) {
1420
+ this.logger.error('Failed to update group picture:', error);
1421
+ return {
1422
+ success: false,
1423
+ error: error.message,
1424
+ };
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Get group invite link
1429
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1430
+ * @returns Full invite link (https://chat.whatsapp.com/...) or null if failed
1431
+ */
1432
+ async getGroupInviteLink(groupJid) {
1433
+ try {
1434
+ if (!this.socket) {
1435
+ throw new Error('Not connected. Call connect() first.');
1436
+ }
1437
+ if (this.connectionState !== 'connected') {
1438
+ throw new Error(`Cannot get invite link. Connection state: ${this.connectionState}`);
1439
+ }
1440
+ if (!groupJid.endsWith('@g.us')) {
1441
+ throw new Error('Invalid group JID. Must end with @g.us');
1442
+ }
1443
+ const code = await this.socket.groupInviteCode(groupJid);
1444
+ return code ? `https://chat.whatsapp.com/${code}` : null;
1445
+ }
1446
+ catch (error) {
1447
+ this.logger.error('Failed to get group invite link:', error);
1448
+ return null;
1449
+ }
1450
+ }
1451
+ /**
1452
+ * Revoke current group invite link and generate a new one
1453
+ * @param groupJid - Group JID (e.g., '123456789@g.us')
1454
+ * @returns New invite link (https://chat.whatsapp.com/...) or null if failed
1455
+ */
1456
+ async revokeGroupInvite(groupJid) {
1457
+ try {
1458
+ if (!this.socket) {
1459
+ throw new Error('Not connected. Call connect() first.');
1460
+ }
1461
+ if (this.connectionState !== 'connected') {
1462
+ throw new Error(`Cannot revoke invite. Connection state: ${this.connectionState}`);
1463
+ }
1464
+ if (!groupJid.endsWith('@g.us')) {
1465
+ throw new Error('Invalid group JID. Must end with @g.us');
1466
+ }
1467
+ const code = await this.socket.groupRevokeInvite(groupJid);
1468
+ return code ? `https://chat.whatsapp.com/${code}` : null;
1469
+ }
1470
+ catch (error) {
1471
+ this.logger.error('Failed to revoke group invite:', error);
1472
+ return null;
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Accept a group invite and join the group
1477
+ * @param inviteCode - Invite code (just the code, or full URL)
1478
+ * @returns Group JID if successful, null if failed
1479
+ */
1480
+ async acceptGroupInvite(inviteCode) {
1481
+ try {
1482
+ if (!this.socket) {
1483
+ throw new Error('Not connected. Call connect() first.');
1484
+ }
1485
+ if (this.connectionState !== 'connected') {
1486
+ throw new Error(`Cannot accept invite. Connection state: ${this.connectionState}`);
1487
+ }
1488
+ // Extract code from URL if full URL was provided
1489
+ let code = inviteCode;
1490
+ if (inviteCode.includes('chat.whatsapp.com/')) {
1491
+ code = inviteCode.split('chat.whatsapp.com/')[1];
1492
+ }
1493
+ const groupJid = await this.socket.groupAcceptInvite(code);
1494
+ return groupJid || null;
1495
+ }
1496
+ catch (error) {
1497
+ this.logger.error('Failed to accept group invite:', error);
1498
+ return null;
1499
+ }
1500
+ }
1501
+ /**
1502
+ * Get group information from invite code without joining
1503
+ * @param inviteCode - Invite code (just the code, or full URL)
1504
+ * @returns GroupInviteInfo if successful, null if failed
1505
+ */
1506
+ async getGroupInviteInfo(inviteCode) {
1507
+ try {
1508
+ if (!this.socket) {
1509
+ throw new Error('Not connected. Call connect() first.');
1510
+ }
1511
+ if (this.connectionState !== 'connected') {
1512
+ throw new Error(`Cannot get invite info. Connection state: ${this.connectionState}`);
1513
+ }
1514
+ // Extract code from URL if full URL was provided
1515
+ let code = inviteCode;
1516
+ if (inviteCode.includes('chat.whatsapp.com/')) {
1517
+ code = inviteCode.split('chat.whatsapp.com/')[1];
1518
+ }
1519
+ const metadata = await this.socket.groupGetInviteInfo(code);
1520
+ return {
1521
+ jid: metadata.id,
1522
+ name: metadata.subject,
1523
+ description: metadata.desc || undefined,
1524
+ participantCount: metadata.size || metadata.participants?.length || 0,
1525
+ createdAt: metadata.creation,
1526
+ };
1527
+ }
1528
+ catch (error) {
1529
+ this.logger.error('Failed to get group invite info:', error);
1530
+ return null;
1531
+ }
1532
+ }
1533
+ // ============================================
1534
+ // Profile Management Methods (v0.8.0)
1535
+ // ============================================
1536
+ /**
1537
+ * Update your own profile picture
1538
+ * @param image - Image source (file path, URL, or Buffer)
1539
+ * @returns ProfileOperationResult indicating success or failure
1540
+ */
1541
+ async updateProfilePicture(image) {
1542
+ try {
1543
+ if (!this.socket) {
1544
+ throw new Error('Not connected. Call connect() first.');
1545
+ }
1546
+ if (this.connectionState !== 'connected') {
1547
+ throw new Error(`Cannot update profile picture. Connection state: ${this.connectionState}`);
1548
+ }
1549
+ const userJid = this.socket.user?.id;
1550
+ if (!userJid) {
1551
+ throw new Error('User JID not available');
1552
+ }
1553
+ const imageContent = Buffer.isBuffer(image) ? image : { url: image };
1554
+ await this.socket.updateProfilePicture(userJid, imageContent);
1555
+ return { success: true };
1556
+ }
1557
+ catch (error) {
1558
+ this.logger.error('Failed to update profile picture:', error);
1559
+ return {
1560
+ success: false,
1561
+ error: error.message,
1562
+ };
1563
+ }
1564
+ }
1565
+ /**
1566
+ * Remove your own profile picture
1567
+ * @returns ProfileOperationResult indicating success or failure
1568
+ */
1569
+ async removeProfilePicture() {
1570
+ try {
1571
+ if (!this.socket) {
1572
+ throw new Error('Not connected. Call connect() first.');
1573
+ }
1574
+ if (this.connectionState !== 'connected') {
1575
+ throw new Error(`Cannot remove profile picture. Connection state: ${this.connectionState}`);
1576
+ }
1577
+ const userJid = this.socket.user?.id;
1578
+ if (!userJid) {
1579
+ throw new Error('User JID not available');
1580
+ }
1581
+ await this.socket.removeProfilePicture(userJid);
1582
+ return { success: true };
1583
+ }
1584
+ catch (error) {
1585
+ this.logger.error('Failed to remove profile picture:', error);
1586
+ return {
1587
+ success: false,
1588
+ error: error.message,
1589
+ };
1590
+ }
1591
+ }
1592
+ /**
1593
+ * Update your profile display name (push name)
1594
+ * @param name - New display name
1595
+ * @returns ProfileOperationResult indicating success or failure
1596
+ */
1597
+ async updateProfileName(name) {
1598
+ try {
1599
+ if (!this.socket) {
1600
+ throw new Error('Not connected. Call connect() first.');
1601
+ }
1602
+ if (this.connectionState !== 'connected') {
1603
+ throw new Error(`Cannot update profile name. Connection state: ${this.connectionState}`);
1604
+ }
1605
+ if (!name || name.trim().length === 0) {
1606
+ throw new Error('Profile name cannot be empty');
1607
+ }
1608
+ await this.socket.updateProfileName(name);
1609
+ return { success: true };
1610
+ }
1611
+ catch (error) {
1612
+ this.logger.error('Failed to update profile name:', error);
1613
+ return {
1614
+ success: false,
1615
+ error: error.message,
1616
+ };
1617
+ }
1618
+ }
1619
+ /**
1620
+ * Update your profile status (About text)
1621
+ * @param status - New status/about text
1622
+ * @returns ProfileOperationResult indicating success or failure
1623
+ */
1624
+ async updateProfileStatus(status) {
1625
+ try {
1626
+ if (!this.socket) {
1627
+ throw new Error('Not connected. Call connect() first.');
1628
+ }
1629
+ if (this.connectionState !== 'connected') {
1630
+ throw new Error(`Cannot update profile status. Connection state: ${this.connectionState}`);
1631
+ }
1632
+ await this.socket.updateProfileStatus(status);
1633
+ return { success: true };
1634
+ }
1635
+ catch (error) {
1636
+ this.logger.error('Failed to update profile status:', error);
1637
+ return {
1638
+ success: false,
1639
+ error: error.message,
1640
+ };
1641
+ }
1642
+ }
1643
+ // ============================================
1644
+ // Label Methods (v0.9.0) - WhatsApp Business only
1645
+ // ============================================
1646
+ /**
1647
+ * Create or edit a label
1648
+ * @param label - Label data (for edit, include the label ID)
1649
+ * @returns LabelOperationResult
1650
+ * @note Requires WhatsApp Business account
1651
+ */
1652
+ async addLabel(label) {
1653
+ try {
1654
+ if (!this.socket) {
1655
+ throw new Error('Not connected. Call connect() first.');
1656
+ }
1657
+ if (this.connectionState !== 'connected') {
1658
+ throw new Error(`Cannot add label. Connection state: ${this.connectionState}`);
1659
+ }
1660
+ // Build label action body
1661
+ const labelAction = {
1662
+ id: label.id,
1663
+ name: label.name,
1664
+ color: label.color,
1665
+ deleted: label.deleted ?? false,
1666
+ };
1667
+ if (label.predefinedId !== undefined) {
1668
+ labelAction.predefinedId = label.predefinedId;
1669
+ }
1670
+ // Use empty string JID for label creation (affects account, not a specific chat)
1671
+ await this.socket.addLabel('', labelAction);
1672
+ return {
1673
+ success: true,
1674
+ labelId: label.id,
1675
+ };
1676
+ }
1677
+ catch (error) {
1678
+ this.logger.error('Failed to add label:', error);
1679
+ return {
1680
+ success: false,
1681
+ error: error.message,
1682
+ };
1683
+ }
1684
+ }
1685
+ /**
1686
+ * Add a label to a chat
1687
+ * @param chatJidOrPhone - Chat JID or phone number
1688
+ * @param labelId - Label ID to add
1689
+ * @returns LabelOperationResult
1690
+ * @note Requires WhatsApp Business account
1691
+ */
1692
+ async addChatLabel(chatJidOrPhone, labelId) {
1693
+ try {
1694
+ if (!this.socket) {
1695
+ throw new Error('Not connected. Call connect() first.');
1696
+ }
1697
+ if (this.connectionState !== 'connected') {
1698
+ throw new Error(`Cannot add chat label. Connection state: ${this.connectionState}`);
1699
+ }
1700
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(chatJidOrPhone);
1701
+ await this.socket.addChatLabel(jid, labelId);
1702
+ return { success: true };
1703
+ }
1704
+ catch (error) {
1705
+ this.logger.error('Failed to add chat label:', error);
1706
+ return {
1707
+ success: false,
1708
+ error: error.message,
1709
+ };
1710
+ }
1711
+ }
1712
+ /**
1713
+ * Remove a label from a chat
1714
+ * @param chatJidOrPhone - Chat JID or phone number
1715
+ * @param labelId - Label ID to remove
1716
+ * @returns LabelOperationResult
1717
+ * @note Requires WhatsApp Business account
1718
+ */
1719
+ async removeChatLabel(chatJidOrPhone, labelId) {
1720
+ try {
1721
+ if (!this.socket) {
1722
+ throw new Error('Not connected. Call connect() first.');
1723
+ }
1724
+ if (this.connectionState !== 'connected') {
1725
+ throw new Error(`Cannot remove chat label. Connection state: ${this.connectionState}`);
1726
+ }
1727
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(chatJidOrPhone);
1728
+ await this.socket.removeChatLabel(jid, labelId);
1729
+ return { success: true };
1730
+ }
1731
+ catch (error) {
1732
+ this.logger.error('Failed to remove chat label:', error);
1733
+ return {
1734
+ success: false,
1735
+ error: error.message,
1736
+ };
1737
+ }
1738
+ }
1739
+ /**
1740
+ * Add a label to a message
1741
+ * @param chatJidOrPhone - Chat JID or phone number where the message is
1742
+ * @param messageId - Message ID to label
1743
+ * @param labelId - Label ID to add
1744
+ * @returns LabelOperationResult
1745
+ * @note Requires WhatsApp Business account
1746
+ */
1747
+ async addMessageLabel(chatJidOrPhone, messageId, labelId) {
1748
+ try {
1749
+ if (!this.socket) {
1750
+ throw new Error('Not connected. Call connect() first.');
1751
+ }
1752
+ if (this.connectionState !== 'connected') {
1753
+ throw new Error(`Cannot add message label. Connection state: ${this.connectionState}`);
1754
+ }
1755
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(chatJidOrPhone);
1756
+ await this.socket.addMessageLabel(jid, messageId, labelId);
1757
+ return { success: true };
1758
+ }
1759
+ catch (error) {
1760
+ this.logger.error('Failed to add message label:', error);
1761
+ return {
1762
+ success: false,
1763
+ error: error.message,
1764
+ };
1765
+ }
1766
+ }
1767
+ /**
1768
+ * Remove a label from a message
1769
+ * @param chatJidOrPhone - Chat JID or phone number where the message is
1770
+ * @param messageId - Message ID to unlabel
1771
+ * @param labelId - Label ID to remove
1772
+ * @returns LabelOperationResult
1773
+ * @note Requires WhatsApp Business account
1774
+ */
1775
+ async removeMessageLabel(chatJidOrPhone, messageId, labelId) {
1776
+ try {
1777
+ if (!this.socket) {
1778
+ throw new Error('Not connected. Call connect() first.');
1779
+ }
1780
+ if (this.connectionState !== 'connected') {
1781
+ throw new Error(`Cannot remove message label. Connection state: ${this.connectionState}`);
1782
+ }
1783
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(chatJidOrPhone);
1784
+ await this.socket.removeMessageLabel(jid, messageId, labelId);
1785
+ return { success: true };
1786
+ }
1787
+ catch (error) {
1788
+ this.logger.error('Failed to remove message label:', error);
1789
+ return {
1790
+ success: false,
1791
+ error: error.message,
1792
+ };
1793
+ }
1794
+ }
1795
+ // ============================================
1796
+ // Catalog/Product Methods (v0.9.0) - WhatsApp Business only
1797
+ // ============================================
1798
+ /**
1799
+ * Get product catalog from a WhatsApp Business account
1800
+ * @param businessJidOrPhone - Business JID or phone number (default: your own catalog)
1801
+ * @param limit - Maximum number of products to fetch (default: 10)
1802
+ * @param cursor - Pagination cursor for fetching next page
1803
+ * @returns ProductCatalog
1804
+ * @note Requires WhatsApp Business account
1805
+ */
1806
+ async getCatalog(businessJidOrPhone, limit = 10, cursor) {
1807
+ try {
1808
+ if (!this.socket) {
1809
+ throw new Error('Not connected. Call connect() first.');
1810
+ }
1811
+ if (this.connectionState !== 'connected') {
1812
+ throw new Error(`Cannot get catalog. Connection state: ${this.connectionState}`);
1813
+ }
1814
+ const jid = businessJidOrPhone ? MessageHandler_1.MessageHandler.formatPhoneToJid(businessJidOrPhone) : undefined;
1815
+ const result = await this.socket.getCatalog({ jid, limit, cursor });
1816
+ const products = (result.products || []).map((p) => ({
1817
+ id: p.id,
1818
+ name: p.name,
1819
+ description: p.description,
1820
+ priceAmount1000: p.priceAmount1000,
1821
+ retailerId: p.retailerId,
1822
+ url: p.url,
1823
+ isHidden: p.isHidden,
1824
+ images: p.images?.map((img) => ({ url: img.url, caption: img.caption })) || [],
1825
+ count: p.count,
1826
+ }));
1827
+ return {
1828
+ success: true,
1829
+ products,
1830
+ nextCursor: result.nextPageCursor,
1831
+ };
1832
+ }
1833
+ catch (error) {
1834
+ this.logger.error('Failed to get catalog:', error);
1835
+ return {
1836
+ success: false,
1837
+ error: error.message,
1838
+ };
1839
+ }
1840
+ }
1841
+ /**
1842
+ * Get product collections from a WhatsApp Business account
1843
+ * @param businessJidOrPhone - Business JID or phone number (default: your own collections)
1844
+ * @param limit - Maximum number of collections to fetch (default: 51)
1845
+ * @returns Array of ProductCollection
1846
+ * @note Requires WhatsApp Business account
1847
+ */
1848
+ async getCollections(businessJidOrPhone, limit = 51) {
1849
+ try {
1850
+ if (!this.socket) {
1851
+ throw new Error('Not connected. Call connect() first.');
1852
+ }
1853
+ if (this.connectionState !== 'connected') {
1854
+ throw new Error(`Cannot get collections. Connection state: ${this.connectionState}`);
1855
+ }
1856
+ const jid = businessJidOrPhone ? MessageHandler_1.MessageHandler.formatPhoneToJid(businessJidOrPhone) : undefined;
1857
+ const result = await this.socket.getCollections(jid, limit);
1858
+ return (result.collections || []).map((col) => ({
1859
+ id: col.id,
1860
+ name: col.name,
1861
+ products: col.products?.map((p) => ({
1862
+ id: p.id,
1863
+ name: p.name,
1864
+ priceAmount1000: p.priceAmount1000,
1865
+ retailerId: p.retailerId,
1866
+ images: p.images?.map((img) => ({ url: img.url, caption: img.caption })) || [],
1867
+ })),
1868
+ }));
1869
+ }
1870
+ catch (error) {
1871
+ this.logger.error('Failed to get collections:', error);
1872
+ return [];
1873
+ }
1874
+ }
1875
+ /**
1876
+ * Create a new product in the catalog
1877
+ * @param options - Product options (name, price, images, etc.)
1878
+ * @returns ProductOperationResult with product ID
1879
+ * @note Requires WhatsApp Business account
1880
+ */
1881
+ async createProduct(options) {
1882
+ try {
1883
+ if (!this.socket) {
1884
+ throw new Error('Not connected. Call connect() first.');
1885
+ }
1886
+ if (this.connectionState !== 'connected') {
1887
+ throw new Error(`Cannot create product. Connection state: ${this.connectionState}`);
1888
+ }
1889
+ // Convert price from smallest unit to Baileys format (multiply by 1000)
1890
+ const priceAmount1000 = options.price * 10;
1891
+ const productData = {
1892
+ name: options.name,
1893
+ priceAmount1000,
1894
+ isHidden: options.isHidden ?? false,
1895
+ };
1896
+ if (options.description) {
1897
+ productData.description = options.description;
1898
+ }
1899
+ if (options.retailerId) {
1900
+ productData.retailerId = options.retailerId;
1901
+ }
1902
+ if (options.url) {
1903
+ productData.url = options.url;
1904
+ }
1905
+ if (options.imageUrls && options.imageUrls.length > 0) {
1906
+ productData.images = options.imageUrls.map((url) => ({ url }));
1907
+ }
1908
+ const result = await this.socket.productCreate(productData);
1909
+ return {
1910
+ success: true,
1911
+ productId: result?.id,
1912
+ };
1913
+ }
1914
+ catch (error) {
1915
+ this.logger.error('Failed to create product:', error);
1916
+ return {
1917
+ success: false,
1918
+ error: error.message,
1919
+ };
1920
+ }
1921
+ }
1922
+ /**
1923
+ * Update an existing product in the catalog
1924
+ * @param productId - Product ID to update
1925
+ * @param options - Product options (name, price, images, etc.)
1926
+ * @returns ProductOperationResult
1927
+ * @note Requires WhatsApp Business account
1928
+ */
1929
+ async updateProduct(productId, options) {
1930
+ try {
1931
+ if (!this.socket) {
1932
+ throw new Error('Not connected. Call connect() first.');
1933
+ }
1934
+ if (this.connectionState !== 'connected') {
1935
+ throw new Error(`Cannot update product. Connection state: ${this.connectionState}`);
1936
+ }
1937
+ // Convert price from smallest unit to Baileys format (multiply by 1000)
1938
+ const priceAmount1000 = options.price * 10;
1939
+ const productData = {
1940
+ priceAmount1000,
1941
+ };
1942
+ if (options.name) {
1943
+ productData.name = options.name;
1944
+ }
1945
+ if (options.description !== undefined) {
1946
+ productData.description = options.description;
1947
+ }
1948
+ if (options.isHidden !== undefined) {
1949
+ productData.isHidden = options.isHidden;
1950
+ }
1951
+ if (options.retailerId) {
1952
+ productData.retailerId = options.retailerId;
1953
+ }
1954
+ if (options.url) {
1955
+ productData.url = options.url;
1956
+ }
1957
+ if (options.imageUrls && options.imageUrls.length > 0) {
1958
+ productData.images = options.imageUrls.map((url) => ({ url }));
1959
+ }
1960
+ const result = await this.socket.productUpdate(productId, productData);
1961
+ return {
1962
+ success: true,
1963
+ productId: result?.id,
1964
+ };
1965
+ }
1966
+ catch (error) {
1967
+ this.logger.error('Failed to update product:', error);
1968
+ return {
1969
+ success: false,
1970
+ error: error.message,
1971
+ };
1972
+ }
1973
+ }
1974
+ /**
1975
+ * Delete products from the catalog
1976
+ * @param productIds - Array of product IDs to delete
1977
+ * @returns ProductOperationResult with deleted count
1978
+ * @note Requires WhatsApp Business account
1979
+ */
1980
+ async deleteProducts(productIds) {
1981
+ try {
1982
+ if (!this.socket) {
1983
+ throw new Error('Not connected. Call connect() first.');
1984
+ }
1985
+ if (this.connectionState !== 'connected') {
1986
+ throw new Error(`Cannot delete products. Connection state: ${this.connectionState}`);
1987
+ }
1988
+ if (productIds.length === 0) {
1989
+ throw new Error('At least one product ID is required');
1990
+ }
1991
+ const result = await this.socket.productDelete(productIds);
1992
+ return {
1993
+ success: true,
1994
+ deletedCount: result.deleted,
1995
+ };
1996
+ }
1997
+ catch (error) {
1998
+ this.logger.error('Failed to delete products:', error);
1999
+ return {
2000
+ success: false,
2001
+ error: error.message,
2002
+ };
2003
+ }
2004
+ }
2005
+ // ============================================
2006
+ // Newsletter/Channel Methods (v0.9.0)
2007
+ // ============================================
2008
+ /**
2009
+ * Create a new newsletter/channel
2010
+ * @param name - Newsletter name
2011
+ * @param description - Newsletter description
2012
+ * @returns NewsletterOperationResult with newsletter ID
2013
+ */
2014
+ async createNewsletter(name, description) {
2015
+ try {
2016
+ if (!this.socket) {
2017
+ throw new Error('Not connected. Call connect() first.');
2018
+ }
2019
+ if (this.connectionState !== 'connected') {
2020
+ throw new Error(`Cannot create newsletter. Connection state: ${this.connectionState}`);
2021
+ }
2022
+ const result = await this.socket.newsletterCreate(name, description || '');
2023
+ return {
2024
+ success: true,
2025
+ newsletterId: result?.id,
2026
+ };
2027
+ }
2028
+ catch (error) {
2029
+ this.logger.error('Failed to create newsletter:', error);
2030
+ return {
2031
+ success: false,
2032
+ error: error.message,
2033
+ };
2034
+ }
2035
+ }
2036
+ /**
2037
+ * Get newsletter metadata
2038
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2039
+ * @returns NewsletterMetadata or null if not found
2040
+ */
2041
+ async getNewsletterMetadata(newsletterId) {
2042
+ try {
2043
+ if (!this.socket) {
2044
+ throw new Error('Not connected. Call connect() first.');
2045
+ }
2046
+ if (this.connectionState !== 'connected') {
2047
+ throw new Error(`Cannot get newsletter metadata. Connection state: ${this.connectionState}`);
2048
+ }
2049
+ if (!newsletterId.endsWith('@newsletter')) {
2050
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2051
+ }
2052
+ const meta = await this.socket.newsletterMetadata('jid', newsletterId);
2053
+ if (!meta) {
2054
+ return null;
2055
+ }
2056
+ // Extract picture URL from the picture object if available
2057
+ const pictureUrl = typeof meta.picture === 'string' ? meta.picture : meta.picture?.url;
2058
+ return {
2059
+ id: meta.id || newsletterId,
2060
+ name: meta.name || '',
2061
+ description: meta.description,
2062
+ pictureUrl,
2063
+ subscribers: meta.subscribers,
2064
+ isCreator: false, // Baileys doesn't provide this
2065
+ isFollowing: false, // Baileys doesn't provide this
2066
+ isMuted: false, // Baileys doesn't provide this
2067
+ createdAt: meta.creation_time,
2068
+ updatedAt: undefined,
2069
+ };
2070
+ }
2071
+ catch (error) {
2072
+ this.logger.error('Failed to get newsletter metadata:', error);
2073
+ return null;
2074
+ }
2075
+ }
2076
+ /**
2077
+ * Follow a newsletter/channel
2078
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2079
+ * @returns boolean indicating success
2080
+ */
2081
+ async followNewsletter(newsletterId) {
2082
+ try {
2083
+ if (!this.socket) {
2084
+ throw new Error('Not connected. Call connect() first.');
2085
+ }
2086
+ if (this.connectionState !== 'connected') {
2087
+ throw new Error(`Cannot follow newsletter. Connection state: ${this.connectionState}`);
2088
+ }
2089
+ if (!newsletterId.endsWith('@newsletter')) {
2090
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2091
+ }
2092
+ await this.socket.newsletterFollow(newsletterId);
2093
+ return true;
2094
+ }
2095
+ catch (error) {
2096
+ this.logger.error('Failed to follow newsletter:', error);
2097
+ return false;
2098
+ }
2099
+ }
2100
+ /**
2101
+ * Unfollow a newsletter/channel
2102
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2103
+ * @returns boolean indicating success
2104
+ */
2105
+ async unfollowNewsletter(newsletterId) {
2106
+ try {
2107
+ if (!this.socket) {
2108
+ throw new Error('Not connected. Call connect() first.');
2109
+ }
2110
+ if (this.connectionState !== 'connected') {
2111
+ throw new Error(`Cannot unfollow newsletter. Connection state: ${this.connectionState}`);
2112
+ }
2113
+ if (!newsletterId.endsWith('@newsletter')) {
2114
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2115
+ }
2116
+ await this.socket.newsletterUnfollow(newsletterId);
2117
+ return true;
2118
+ }
2119
+ catch (error) {
2120
+ this.logger.error('Failed to unfollow newsletter:', error);
2121
+ return false;
2122
+ }
2123
+ }
2124
+ /**
2125
+ * Mute a newsletter/channel
2126
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2127
+ * @returns boolean indicating success
2128
+ */
2129
+ async muteNewsletter(newsletterId) {
2130
+ try {
2131
+ if (!this.socket) {
2132
+ throw new Error('Not connected. Call connect() first.');
2133
+ }
2134
+ if (this.connectionState !== 'connected') {
2135
+ throw new Error(`Cannot mute newsletter. Connection state: ${this.connectionState}`);
2136
+ }
2137
+ if (!newsletterId.endsWith('@newsletter')) {
2138
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2139
+ }
2140
+ await this.socket.newsletterMute(newsletterId);
2141
+ return true;
2142
+ }
2143
+ catch (error) {
2144
+ this.logger.error('Failed to mute newsletter:', error);
2145
+ return false;
2146
+ }
2147
+ }
2148
+ /**
2149
+ * Unmute a newsletter/channel
2150
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2151
+ * @returns boolean indicating success
2152
+ */
2153
+ async unmuteNewsletter(newsletterId) {
2154
+ try {
2155
+ if (!this.socket) {
2156
+ throw new Error('Not connected. Call connect() first.');
2157
+ }
2158
+ if (this.connectionState !== 'connected') {
2159
+ throw new Error(`Cannot unmute newsletter. Connection state: ${this.connectionState}`);
2160
+ }
2161
+ if (!newsletterId.endsWith('@newsletter')) {
2162
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2163
+ }
2164
+ await this.socket.newsletterUnmute(newsletterId);
2165
+ return true;
2166
+ }
2167
+ catch (error) {
2168
+ this.logger.error('Failed to unmute newsletter:', error);
2169
+ return false;
2170
+ }
2171
+ }
2172
+ /**
2173
+ * Update newsletter name
2174
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2175
+ * @param name - New newsletter name
2176
+ * @returns boolean indicating success
2177
+ */
2178
+ async updateNewsletterName(newsletterId, name) {
2179
+ try {
2180
+ if (!this.socket) {
2181
+ throw new Error('Not connected. Call connect() first.');
2182
+ }
2183
+ if (this.connectionState !== 'connected') {
2184
+ throw new Error(`Cannot update newsletter name. Connection state: ${this.connectionState}`);
2185
+ }
2186
+ if (!newsletterId.endsWith('@newsletter')) {
2187
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2188
+ }
2189
+ await this.socket.newsletterUpdateName(newsletterId, name);
2190
+ return true;
2191
+ }
2192
+ catch (error) {
2193
+ this.logger.error('Failed to update newsletter name:', error);
2194
+ return false;
2195
+ }
2196
+ }
2197
+ /**
2198
+ * Update newsletter description
2199
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2200
+ * @param description - New newsletter description
2201
+ * @returns boolean indicating success
2202
+ */
2203
+ async updateNewsletterDescription(newsletterId, description) {
2204
+ try {
2205
+ if (!this.socket) {
2206
+ throw new Error('Not connected. Call connect() first.');
2207
+ }
2208
+ if (this.connectionState !== 'connected') {
2209
+ throw new Error(`Cannot update newsletter description. Connection state: ${this.connectionState}`);
2210
+ }
2211
+ if (!newsletterId.endsWith('@newsletter')) {
2212
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2213
+ }
2214
+ await this.socket.newsletterUpdateDescription(newsletterId, description);
2215
+ return true;
2216
+ }
2217
+ catch (error) {
2218
+ this.logger.error('Failed to update newsletter description:', error);
2219
+ return false;
2220
+ }
2221
+ }
2222
+ /**
2223
+ * Update newsletter picture
2224
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2225
+ * @param image - Image source (file path, URL, or Buffer)
2226
+ * @returns boolean indicating success
2227
+ */
2228
+ async updateNewsletterPicture(newsletterId, image) {
2229
+ try {
2230
+ if (!this.socket) {
2231
+ throw new Error('Not connected. Call connect() first.');
2232
+ }
2233
+ if (this.connectionState !== 'connected') {
2234
+ throw new Error(`Cannot update newsletter picture. Connection state: ${this.connectionState}`);
2235
+ }
2236
+ if (!newsletterId.endsWith('@newsletter')) {
2237
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2238
+ }
2239
+ const imageContent = Buffer.isBuffer(image) ? image : { url: image };
2240
+ await this.socket.newsletterUpdatePicture(newsletterId, imageContent);
2241
+ return true;
2242
+ }
2243
+ catch (error) {
2244
+ this.logger.error('Failed to update newsletter picture:', error);
2245
+ return false;
2246
+ }
2247
+ }
2248
+ /**
2249
+ * Remove newsletter picture
2250
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2251
+ * @returns boolean indicating success
2252
+ */
2253
+ async removeNewsletterPicture(newsletterId) {
2254
+ try {
2255
+ if (!this.socket) {
2256
+ throw new Error('Not connected. Call connect() first.');
2257
+ }
2258
+ if (this.connectionState !== 'connected') {
2259
+ throw new Error(`Cannot remove newsletter picture. Connection state: ${this.connectionState}`);
2260
+ }
2261
+ if (!newsletterId.endsWith('@newsletter')) {
2262
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2263
+ }
2264
+ await this.socket.newsletterRemovePicture(newsletterId);
2265
+ return true;
2266
+ }
2267
+ catch (error) {
2268
+ this.logger.error('Failed to remove newsletter picture:', error);
2269
+ return false;
2270
+ }
2271
+ }
2272
+ /**
2273
+ * React to a newsletter message
2274
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2275
+ * @param messageId - Server message ID to react to
2276
+ * @param emoji - Emoji to react with (empty string removes reaction)
2277
+ * @returns boolean indicating success
2278
+ */
2279
+ async reactToNewsletterMessage(newsletterId, messageId, emoji) {
2280
+ try {
2281
+ if (!this.socket) {
2282
+ throw new Error('Not connected. Call connect() first.');
2283
+ }
2284
+ if (this.connectionState !== 'connected') {
2285
+ throw new Error(`Cannot react to newsletter message. Connection state: ${this.connectionState}`);
2286
+ }
2287
+ if (!newsletterId.endsWith('@newsletter')) {
2288
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2289
+ }
2290
+ await this.socket.newsletterReactMessage(newsletterId, messageId, emoji);
2291
+ return true;
2292
+ }
2293
+ catch (error) {
2294
+ this.logger.error('Failed to react to newsletter message:', error);
2295
+ return false;
2296
+ }
2297
+ }
2298
+ /**
2299
+ * Fetch messages from a newsletter
2300
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2301
+ * @param count - Number of messages to fetch (default: 50)
2302
+ * @param since - Timestamp to fetch messages since (default: now, backward)
2303
+ * @param after - Cursor for pagination (default: 0)
2304
+ * @returns NewsletterMessagesResult
2305
+ */
2306
+ async fetchNewsletterMessages(newsletterId, count = 50, since, after = 0) {
2307
+ try {
2308
+ if (!this.socket) {
2309
+ throw new Error('Not connected. Call connect() first.');
2310
+ }
2311
+ if (this.connectionState !== 'connected') {
2312
+ throw new Error(`Cannot fetch newsletter messages. Connection state: ${this.connectionState}`);
2313
+ }
2314
+ if (!newsletterId.endsWith('@newsletter')) {
2315
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2316
+ }
2317
+ const messages = await this.socket.newsletterFetchMessages(newsletterId, count, since || Date.now(), after);
2318
+ const normalizedMessages = (messages || []).map((msg) => ({
2319
+ id: msg.key?.id || '',
2320
+ newsletterId,
2321
+ content: msg.message?.conversation || msg.message?.extendedTextMessage?.text,
2322
+ timestamp: msg.messageTimestamp || 0,
2323
+ mediaUrl: msg.message?.imageMessage?.url || msg.message?.videoMessage?.url,
2324
+ mediaType: msg.message?.imageMessage ? 'image' : msg.message?.videoMessage ? 'video' : undefined,
2325
+ }));
2326
+ return {
2327
+ success: true,
2328
+ messages: normalizedMessages,
2329
+ };
2330
+ }
2331
+ catch (error) {
2332
+ this.logger.error('Failed to fetch newsletter messages:', error);
2333
+ return {
2334
+ success: false,
2335
+ error: error.message,
2336
+ };
2337
+ }
2338
+ }
2339
+ /**
2340
+ * Subscribe to live newsletter updates
2341
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2342
+ * @returns boolean indicating success
2343
+ */
2344
+ async subscribeNewsletterUpdates(newsletterId) {
2345
+ try {
2346
+ if (!this.socket) {
2347
+ throw new Error('Not connected. Call connect() first.');
2348
+ }
2349
+ if (this.connectionState !== 'connected') {
2350
+ throw new Error(`Cannot subscribe to newsletter updates. Connection state: ${this.connectionState}`);
2351
+ }
2352
+ if (!newsletterId.endsWith('@newsletter')) {
2353
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2354
+ }
2355
+ await this.socket.subscribeNewsletterUpdates(newsletterId);
2356
+ return true;
2357
+ }
2358
+ catch (error) {
2359
+ this.logger.error('Failed to subscribe to newsletter updates:', error);
2360
+ return false;
2361
+ }
2362
+ }
2363
+ /**
2364
+ * Get newsletter subscriber and admin count
2365
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2366
+ * @returns NewsletterSubscriptionInfo or null if failed
2367
+ */
2368
+ async getNewsletterSubscribers(newsletterId) {
2369
+ try {
2370
+ if (!this.socket) {
2371
+ throw new Error('Not connected. Call connect() first.');
2372
+ }
2373
+ if (this.connectionState !== 'connected') {
2374
+ throw new Error(`Cannot get newsletter subscribers. Connection state: ${this.connectionState}`);
2375
+ }
2376
+ if (!newsletterId.endsWith('@newsletter')) {
2377
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2378
+ }
2379
+ const result = await this.socket.newsletterSubscribers(newsletterId);
2380
+ return {
2381
+ subscribers: result?.subscribers,
2382
+ adminCount: undefined,
2383
+ };
2384
+ }
2385
+ catch (error) {
2386
+ this.logger.error('Failed to get newsletter subscribers:', error);
2387
+ return null;
2388
+ }
2389
+ }
2390
+ /**
2391
+ * Get newsletter admin count
2392
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2393
+ * @returns Admin count or null if failed
2394
+ */
2395
+ async getNewsletterAdminCount(newsletterId) {
2396
+ try {
2397
+ if (!this.socket) {
2398
+ throw new Error('Not connected. Call connect() first.');
2399
+ }
2400
+ if (this.connectionState !== 'connected') {
2401
+ throw new Error(`Cannot get newsletter admin count. Connection state: ${this.connectionState}`);
2402
+ }
2403
+ if (!newsletterId.endsWith('@newsletter')) {
2404
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2405
+ }
2406
+ const count = await this.socket.newsletterAdminCount(newsletterId);
2407
+ return count || 0;
2408
+ }
2409
+ catch (error) {
2410
+ this.logger.error('Failed to get newsletter admin count:', error);
2411
+ return null;
2412
+ }
2413
+ }
2414
+ /**
2415
+ * Change newsletter owner (transfer ownership)
2416
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2417
+ * @param newOwnerJid - New owner's JID
2418
+ * @returns boolean indicating success
2419
+ */
2420
+ async changeNewsletterOwner(newsletterId, newOwnerJid) {
2421
+ try {
2422
+ if (!this.socket) {
2423
+ throw new Error('Not connected. Call connect() first.');
2424
+ }
2425
+ if (this.connectionState !== 'connected') {
2426
+ throw new Error(`Cannot change newsletter owner. Connection state: ${this.connectionState}`);
2427
+ }
2428
+ if (!newsletterId.endsWith('@newsletter')) {
2429
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2430
+ }
2431
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(newOwnerJid);
2432
+ await this.socket.newsletterChangeOwner(newsletterId, jid);
2433
+ return true;
2434
+ }
2435
+ catch (error) {
2436
+ this.logger.error('Failed to change newsletter owner:', error);
2437
+ return false;
2438
+ }
2439
+ }
2440
+ /**
2441
+ * Demote a newsletter admin
2442
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2443
+ * @param adminJid - Admin's JID to demote
2444
+ * @returns boolean indicating success
2445
+ */
2446
+ async demoteNewsletterAdmin(newsletterId, adminJid) {
2447
+ try {
2448
+ if (!this.socket) {
2449
+ throw new Error('Not connected. Call connect() first.');
2450
+ }
2451
+ if (this.connectionState !== 'connected') {
2452
+ throw new Error(`Cannot demote newsletter admin. Connection state: ${this.connectionState}`);
2453
+ }
2454
+ if (!newsletterId.endsWith('@newsletter')) {
2455
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2456
+ }
2457
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(adminJid);
2458
+ await this.socket.newsletterDemote(newsletterId, jid);
2459
+ return true;
2460
+ }
2461
+ catch (error) {
2462
+ this.logger.error('Failed to demote newsletter admin:', error);
2463
+ return false;
2464
+ }
2465
+ }
2466
+ /**
2467
+ * Delete a newsletter/channel
2468
+ * @param newsletterId - Newsletter JID (e.g., '1234567890@newsletter')
2469
+ * @returns boolean indicating success
2470
+ */
2471
+ async deleteNewsletter(newsletterId) {
2472
+ try {
2473
+ if (!this.socket) {
2474
+ throw new Error('Not connected. Call connect() first.');
2475
+ }
2476
+ if (this.connectionState !== 'connected') {
2477
+ throw new Error(`Cannot delete newsletter. Connection state: ${this.connectionState}`);
2478
+ }
2479
+ if (!newsletterId.endsWith('@newsletter')) {
2480
+ throw new Error('Invalid newsletter ID. Must end with @newsletter');
2481
+ }
2482
+ await this.socket.newsletterDelete(newsletterId);
2483
+ return true;
2484
+ }
2485
+ catch (error) {
2486
+ this.logger.error('Failed to delete newsletter:', error);
2487
+ return false;
2488
+ }
2489
+ }
2490
+ // ============================================
2491
+ // Contact Management Methods (v0.9.0)
2492
+ // ============================================
2493
+ /**
2494
+ * Add or edit a contact
2495
+ * @param contact - Contact data (phone, name, etc.)
2496
+ * @returns ContactOperationResult
2497
+ */
2498
+ async addOrEditContact(contact) {
2499
+ try {
2500
+ if (!this.socket) {
2501
+ throw new Error('Not connected. Call connect() first.');
2502
+ }
2503
+ if (this.connectionState !== 'connected') {
2504
+ throw new Error(`Cannot add/edit contact. Connection state: ${this.connectionState}`);
2505
+ }
2506
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(contact.phone);
2507
+ // Build contact action for Baileys
2508
+ const contactAction = {
2509
+ displayName: contact.name,
2510
+ };
2511
+ if (contact.firstName) {
2512
+ contactAction.givenName = contact.firstName;
2513
+ }
2514
+ if (contact.lastName) {
2515
+ contactAction.familyName = contact.lastName;
2516
+ }
2517
+ await this.socket.addOrEditContact(jid, contactAction);
2518
+ return { success: true };
2519
+ }
2520
+ catch (error) {
2521
+ this.logger.error('Failed to add/edit contact:', error);
2522
+ return {
2523
+ success: false,
2524
+ error: error.message,
2525
+ };
2526
+ }
2527
+ }
2528
+ /**
2529
+ * Remove a contact
2530
+ * @param phone - Contact's phone number (with country code)
2531
+ * @returns ContactOperationResult
2532
+ */
2533
+ async removeContact(phone) {
2534
+ try {
2535
+ if (!this.socket) {
2536
+ throw new Error('Not connected. Call connect() first.');
2537
+ }
2538
+ if (this.connectionState !== 'connected') {
2539
+ throw new Error(`Cannot remove contact. Connection state: ${this.connectionState}`);
2540
+ }
2541
+ const jid = MessageHandler_1.MessageHandler.formatPhoneToJid(phone);
2542
+ await this.socket.removeContact(jid);
2543
+ return { success: true };
2544
+ }
2545
+ catch (error) {
2546
+ this.logger.error('Failed to remove contact:', error);
2547
+ return {
2548
+ success: false,
2549
+ error: error.message,
2550
+ };
2551
+ }
2552
+ }
2553
+ /**
2554
+ * Get audio MIME type from file extension
2555
+ */
2556
+ getAudioMimetypeFromFileName(fileName) {
2557
+ const ext = path.extname(fileName).toLowerCase();
2558
+ const mimeTypes = {
2559
+ '.mp3': 'audio/mpeg',
2560
+ '.mp4': 'audio/mp4',
2561
+ '.m4a': 'audio/mp4',
2562
+ '.ogg': 'audio/ogg; codecs=opus',
2563
+ '.opus': 'audio/ogg; codecs=opus',
2564
+ '.wav': 'audio/wav',
2565
+ '.aac': 'audio/aac',
2566
+ '.flac': 'audio/flac',
2567
+ };
2568
+ return mimeTypes[ext] || 'audio/mp4';
2569
+ }
2570
+ /**
2571
+ * Get MIME type from file extension
2572
+ */
2573
+ getMimetypeFromFileName(fileName) {
2574
+ const ext = path.extname(fileName).toLowerCase();
2575
+ const mimeTypes = {
2576
+ '.pdf': 'application/pdf',
2577
+ '.doc': 'application/msword',
2578
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2579
+ '.xls': 'application/vnd.ms-excel',
2580
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2581
+ '.ppt': 'application/vnd.ms-powerpoint',
2582
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2583
+ '.txt': 'text/plain',
2584
+ '.csv': 'text/csv',
2585
+ '.json': 'application/json',
2586
+ '.xml': 'application/xml',
2587
+ '.zip': 'application/zip',
2588
+ '.rar': 'application/x-rar-compressed',
2589
+ '.7z': 'application/x-7z-compressed',
2590
+ '.tar': 'application/x-tar',
2591
+ '.gz': 'application/gzip',
2592
+ };
2593
+ return mimeTypes[ext] || 'application/octet-stream';
2594
+ }
2595
+ /**
2596
+ * Disconnect from WhatsApp
2597
+ */
2598
+ async disconnect() {
2599
+ if (this.reconnectTimer) {
2600
+ clearTimeout(this.reconnectTimer);
2601
+ this.reconnectTimer = null;
2602
+ }
2603
+ if (this.socket) {
2604
+ await this.socket.logout();
2605
+ this.socket = null;
2606
+ }
2607
+ this.updateConnectionState('disconnected');
2608
+ this.logger.info('Disconnected');
2609
+ }
2610
+ /**
2611
+ * Get current connection state
2612
+ */
2613
+ getConnectionState() {
2614
+ return this.connectionState;
2615
+ }
2616
+ /**
2617
+ * Get instance ID
2618
+ */
2619
+ getInstanceId() {
2620
+ return this.options.instanceId;
2621
+ }
2622
+ /**
2623
+ * Check if connected
2624
+ */
2625
+ isConnected() {
2626
+ return this.connectionState === 'connected';
2627
+ }
2628
+ // TypeScript event emitter type safety
2629
+ on(event, listener) {
2630
+ return super.on(event, listener);
2631
+ }
2632
+ emit(event, ...args) {
2633
+ return super.emit(event, ...args);
2634
+ }
2635
+ }
2636
+ exports.MiawClient = MiawClient;
2637
+ //# sourceMappingURL=MiawClient.js.map