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.
- package/CHANGELOG.md +305 -0
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/USAGE.md +1399 -0
- package/babel.config.js +5 -0
- package/dist/client/MiawClient.d.ts +580 -0
- package/dist/client/MiawClient.d.ts.map +1 -0
- package/dist/client/MiawClient.js +2637 -0
- package/dist/client/MiawClient.js.map +1 -0
- package/dist/handlers/AuthHandler.d.ts +21 -0
- package/dist/handlers/AuthHandler.d.ts.map +1 -0
- package/dist/handlers/AuthHandler.js +30 -0
- package/dist/handlers/AuthHandler.js.map +1 -0
- package/dist/handlers/MessageHandler.d.ts +45 -0
- package/dist/handlers/MessageHandler.d.ts.map +1 -0
- package/dist/handlers/MessageHandler.js +185 -0
- package/dist/handlers/MessageHandler.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +632 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +44 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/storage.d.ts +25 -0
- package/dist/utils/storage.d.ts.map +1 -0
- package/dist/utils/storage.js +60 -0
- package/dist/utils/storage.js.map +1 -0
- package/package.json +44 -0
- package/tsconfig.json +24 -0
- package/tsconfig.test.json +13 -0
|
@@ -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
|