@roidev/kachina-md 2.1.0 → 2.1.2
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/lib/client/Client.js +333 -30
- package/lib/handlers/PluginHandler.js +110 -0
- package/lib/helpers/database.js +140 -0
- package/lib/helpers/index.js +85 -0
- package/lib/helpers/logger.js +69 -0
- package/lib/helpers/serialize.js +43 -0
- package/lib/helpers/sticker.js +52 -7
- package/lib/index.d.ts +265 -0
- package/package.json +4 -2
package/lib/client/Client.js
CHANGED
|
@@ -15,10 +15,58 @@ import {
|
|
|
15
15
|
createSticker,
|
|
16
16
|
StickerTypes
|
|
17
17
|
} from '../helpers/sticker.js';
|
|
18
|
-
|
|
18
|
+
import chalk from 'chalk';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} ClientOptions
|
|
22
|
+
* @property {string} [sessionId='kachina-session'] - Session ID for storing authentication data
|
|
23
|
+
* @property {string} [phoneNumber=''] - Phone number for pairing method (format: 628123456789)
|
|
24
|
+
* @property {'qr'|'pairing'} [loginMethod='qr'] - Login method: 'qr' for QR code, 'pairing' for pairing code
|
|
25
|
+
* @property {Array<string>} [browser=['Kachina-MD', 'Chrome', '1.0.0']] - Browser identification
|
|
26
|
+
* @property {Object} [logger] - Pino logger instance
|
|
27
|
+
* @property {string} [prefix='!'] - Command prefix for plugin system
|
|
28
|
+
* @property {Object} [store] - Optional message store for caching
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Main WhatsApp bot client class
|
|
33
|
+
*
|
|
34
|
+
* @class Client
|
|
35
|
+
* @extends EventEmitter
|
|
36
|
+
*
|
|
37
|
+
* @fires Client#ready - Emitted when bot is connected and ready
|
|
38
|
+
* @fires Client#message - Emitted when a new message is received
|
|
39
|
+
* @fires Client#group.update - Emitted when group participants are updated
|
|
40
|
+
* @fires Client#groups.update - Emitted when group info is updated
|
|
41
|
+
* @fires Client#call - Emitted when receiving a call
|
|
42
|
+
* @fires Client#pairing.code - Emitted when pairing code is generated (pairing mode only)
|
|
43
|
+
* @fires Client#pairing.error - Emitted when pairing code request fails
|
|
44
|
+
* @fires Client#reconnecting - Emitted when bot is reconnecting
|
|
45
|
+
* @fires Client#connecting - Emitted when bot is connecting
|
|
46
|
+
* @fires Client#logout - Emitted when bot is logged out
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* // QR Code login
|
|
50
|
+
* const client = new Client({
|
|
51
|
+
* sessionId: 'my-bot',
|
|
52
|
+
* loginMethod: 'qr'
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // Pairing code login
|
|
57
|
+
* const client = new Client({
|
|
58
|
+
* sessionId: 'my-bot',
|
|
59
|
+
* phoneNumber: '628123456789',
|
|
60
|
+
* loginMethod: 'pairing'
|
|
61
|
+
* });
|
|
62
|
+
*/
|
|
19
63
|
export class Client extends EventEmitter {
|
|
20
64
|
static StickerTypes = StickerTypes;
|
|
21
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Creates a new Client instance
|
|
68
|
+
* @param {ClientOptions} [options={}] - Client configuration options
|
|
69
|
+
*/
|
|
22
70
|
constructor(options = {}) {
|
|
23
71
|
super();
|
|
24
72
|
|
|
@@ -37,6 +85,18 @@ export class Client extends EventEmitter {
|
|
|
37
85
|
this.pluginHandler = new PluginHandler(this);
|
|
38
86
|
}
|
|
39
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Start the WhatsApp bot connection
|
|
90
|
+
* Initializes the socket connection and handles authentication
|
|
91
|
+
* @async
|
|
92
|
+
* @returns {Promise<Object>} The initialized socket connection
|
|
93
|
+
* @throws {Error} If phone number is invalid (pairing mode)
|
|
94
|
+
* @example
|
|
95
|
+
* await client.start();
|
|
96
|
+
* client.on('ready', (user) => {
|
|
97
|
+
* console.log('Bot ready:', user.id);
|
|
98
|
+
* });
|
|
99
|
+
*/
|
|
40
100
|
async start() {
|
|
41
101
|
const { state, saveCreds } = await useMultiFileAuthState(this.config.sessionId);
|
|
42
102
|
const { version } = await fetchLatestBaileysVersion();
|
|
@@ -71,35 +131,61 @@ export class Client extends EventEmitter {
|
|
|
71
131
|
throw new Error('Phone number is required for pairing method. Example: { phoneNumber: "628123456789" }');
|
|
72
132
|
}
|
|
73
133
|
|
|
74
|
-
// Format phone number (remove + and spaces)
|
|
75
134
|
const phoneNumber = this.config.phoneNumber.replace(/[^0-9]/g, '');
|
|
76
135
|
|
|
77
136
|
if (phoneNumber.length < 10) {
|
|
78
137
|
throw new Error('Invalid phone number format. Use country code without +. Example: 628123456789');
|
|
79
138
|
}
|
|
80
139
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
140
|
+
console.log('🔐 Initiating pairing code process...');
|
|
141
|
+
|
|
142
|
+
// Request pairing code with retry mechanism
|
|
143
|
+
const requestPairingWithRetry = async () => {
|
|
144
|
+
const maxRetries = 3;
|
|
145
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
146
|
+
|
|
147
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
148
|
+
try {
|
|
149
|
+
// Wait for socket to be ready
|
|
150
|
+
await delay(attempt === 0 ? 5000 : 2000);
|
|
151
|
+
|
|
152
|
+
const code = await this.sock.requestPairingCode(phoneNumber);
|
|
153
|
+
this.emit('pairing.code', code);
|
|
154
|
+
|
|
155
|
+
// Log to console with formatting
|
|
156
|
+
console.log('\n┌─────────────────────────────────────┐');
|
|
157
|
+
console.log('│ WhatsApp Pairing Code │');
|
|
158
|
+
console.log('├─────────────────────────────────────┤');
|
|
159
|
+
console.log(`│ Code: ${chalk.bgYellowBright.white(code)} │`);
|
|
160
|
+
console.log('└─────────────────────────────────────┘');
|
|
161
|
+
console.log('\nSteps to pair:');
|
|
162
|
+
console.log('1. Open WhatsApp on your phone');
|
|
163
|
+
console.log('2. Go to Settings > Linked Devices');
|
|
164
|
+
console.log('3. Tap "Link a Device"');
|
|
165
|
+
console.log('4. Enter the code above\n');
|
|
166
|
+
console.log('⚠️ Make sure the phone number matches your WhatsApp account');
|
|
167
|
+
|
|
168
|
+
return;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const isLastAttempt = attempt === maxRetries - 1;
|
|
171
|
+
console.error(`Pairing attempt ${attempt + 1}/${maxRetries} failed:`, error.message);
|
|
172
|
+
|
|
173
|
+
this.emit('pairing.error', error);
|
|
174
|
+
|
|
175
|
+
if (isLastAttempt) {
|
|
176
|
+
console.error('❌ Max pairing retries reached. Please try again later.');
|
|
177
|
+
throw error; // Re-throw on last attempt
|
|
178
|
+
} else {
|
|
179
|
+
console.log(`⏳ Retrying in 2 seconds...`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
101
182
|
}
|
|
102
|
-
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Execute pairing request (non-blocking)
|
|
186
|
+
requestPairingWithRetry().catch(err => {
|
|
187
|
+
console.error('Failed to complete pairing process:', err.message);
|
|
188
|
+
});
|
|
103
189
|
}
|
|
104
190
|
|
|
105
191
|
this.sock.ev.on('connection.update', async (update) => {
|
|
@@ -135,6 +221,15 @@ export class Client extends EventEmitter {
|
|
|
135
221
|
return this.sock;
|
|
136
222
|
}
|
|
137
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Handle connection updates from WhatsApp socket
|
|
226
|
+
* @private
|
|
227
|
+
* @async
|
|
228
|
+
* @param {Object} update - Connection update object
|
|
229
|
+
* @param {string} [update.connection] - Connection status: 'open', 'close', 'connecting'
|
|
230
|
+
* @param {Object} [update.lastDisconnect] - Last disconnect information
|
|
231
|
+
* @param {string} [update.qr] - QR code data for scanning
|
|
232
|
+
*/
|
|
138
233
|
async handleConnectionUpdate(update) {
|
|
139
234
|
const { connection, lastDisconnect, qr } = update;
|
|
140
235
|
|
|
@@ -170,15 +265,45 @@ export class Client extends EventEmitter {
|
|
|
170
265
|
}
|
|
171
266
|
}
|
|
172
267
|
|
|
173
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Send a raw message to WhatsApp
|
|
270
|
+
* @async
|
|
271
|
+
* @param {string} jid - WhatsApp JID (chat ID) - format: number@s.whatsapp.net or groupId@g.us
|
|
272
|
+
* @param {Object} content - Message content object (text, image, video, etc.)
|
|
273
|
+
* @param {Object} [options={}] - Additional options for message
|
|
274
|
+
* @returns {Promise<Object>} Sent message object
|
|
275
|
+
* @example
|
|
276
|
+
* await client.sendMessage('628xxx@s.whatsapp.net', { text: 'Hello' });
|
|
277
|
+
*/
|
|
174
278
|
async sendMessage(jid, content, options = {}) {
|
|
175
279
|
return await this.sock.sendMessage(jid, content, options);
|
|
176
280
|
}
|
|
177
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Send a text message
|
|
284
|
+
* @async
|
|
285
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
286
|
+
* @param {string} text - Text message content
|
|
287
|
+
* @param {Object} [options={}] - Additional options
|
|
288
|
+
* @returns {Promise<Object>} Sent message object
|
|
289
|
+
* @example
|
|
290
|
+
* await client.sendText('628xxx@s.whatsapp.net', 'Hello World!');
|
|
291
|
+
*/
|
|
178
292
|
async sendText(jid, text, options = {}) {
|
|
179
293
|
return await this.sendMessage(jid, { text }, options);
|
|
180
294
|
}
|
|
181
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Send an image message
|
|
298
|
+
* @async
|
|
299
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
300
|
+
* @param {Buffer|string} buffer - Image buffer or file path/URL
|
|
301
|
+
* @param {string} [caption=''] - Image caption
|
|
302
|
+
* @param {Object} [options={}] - Additional options
|
|
303
|
+
* @returns {Promise<Object>} Sent message object
|
|
304
|
+
* @example
|
|
305
|
+
* await client.sendImage('628xxx@s.whatsapp.net', imageBuffer, 'My Image');
|
|
306
|
+
*/
|
|
182
307
|
async sendImage(jid, buffer, caption = '', options = {}) {
|
|
183
308
|
const content = {
|
|
184
309
|
image: buffer,
|
|
@@ -188,6 +313,17 @@ export class Client extends EventEmitter {
|
|
|
188
313
|
return await this.sendMessage(jid, content, options);
|
|
189
314
|
}
|
|
190
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Send a video message
|
|
318
|
+
* @async
|
|
319
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
320
|
+
* @param {Buffer|string} buffer - Video buffer or file path/URL
|
|
321
|
+
* @param {string} [caption=''] - Video caption
|
|
322
|
+
* @param {Object} [options={}] - Additional options
|
|
323
|
+
* @returns {Promise<Object>} Sent message object
|
|
324
|
+
* @example
|
|
325
|
+
* await client.sendVideo('628xxx@s.whatsapp.net', videoBuffer, 'My Video');
|
|
326
|
+
*/
|
|
191
327
|
async sendVideo(jid, buffer, caption = '', options = {}) {
|
|
192
328
|
const content = {
|
|
193
329
|
video: buffer,
|
|
@@ -197,6 +333,21 @@ export class Client extends EventEmitter {
|
|
|
197
333
|
return await this.sendMessage(jid, content, options);
|
|
198
334
|
}
|
|
199
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Send an audio message
|
|
338
|
+
* @async
|
|
339
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
340
|
+
* @param {Buffer|string} buffer - Audio buffer or file path/URL
|
|
341
|
+
* @param {Object} [options={}] - Additional options
|
|
342
|
+
* @param {string} [options.mimetype='audio/mp4'] - Audio mime type
|
|
343
|
+
* @param {boolean} [options.ptt=false] - Push to talk (voice note)
|
|
344
|
+
* @returns {Promise<Object>} Sent message object
|
|
345
|
+
* @example
|
|
346
|
+
* // Send as audio file
|
|
347
|
+
* await client.sendAudio('628xxx@s.whatsapp.net', audioBuffer);
|
|
348
|
+
* // Send as voice note
|
|
349
|
+
* await client.sendAudio('628xxx@s.whatsapp.net', audioBuffer, { ptt: true });
|
|
350
|
+
*/
|
|
200
351
|
async sendAudio(jid, buffer, options = {}) {
|
|
201
352
|
const content = {
|
|
202
353
|
audio: buffer,
|
|
@@ -207,6 +358,18 @@ export class Client extends EventEmitter {
|
|
|
207
358
|
return await this.sendMessage(jid, content, options);
|
|
208
359
|
}
|
|
209
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Send a document message
|
|
363
|
+
* @async
|
|
364
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
365
|
+
* @param {Buffer|string} buffer - Document buffer or file path/URL
|
|
366
|
+
* @param {string} filename - Document filename with extension
|
|
367
|
+
* @param {string} mimetype - Document mime type (e.g., 'application/pdf')
|
|
368
|
+
* @param {Object} [options={}] - Additional options
|
|
369
|
+
* @returns {Promise<Object>} Sent message object
|
|
370
|
+
* @example
|
|
371
|
+
* await client.sendDocument('628xxx@s.whatsapp.net', pdfBuffer, 'file.pdf', 'application/pdf');
|
|
372
|
+
*/
|
|
210
373
|
async sendDocument(jid, buffer, filename, mimetype, options = {}) {
|
|
211
374
|
return await this.sendMessage(jid, {
|
|
212
375
|
document: buffer,
|
|
@@ -216,7 +379,22 @@ export class Client extends EventEmitter {
|
|
|
216
379
|
});
|
|
217
380
|
}
|
|
218
381
|
|
|
219
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Send a sticker message
|
|
384
|
+
* @async
|
|
385
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
386
|
+
* @param {Buffer|string} buffer - Image buffer or file path/URL to convert to sticker
|
|
387
|
+
* @param {Object} [options={}] - Sticker options
|
|
388
|
+
* @param {string} [options.pack] - Sticker pack name
|
|
389
|
+
* @param {string} [options.author] - Sticker author name
|
|
390
|
+
* @param {string} [options.type] - Sticker type from Client.StickerTypes
|
|
391
|
+
* @returns {Promise<Object>} Sent message object
|
|
392
|
+
* @example
|
|
393
|
+
* await client.sendSticker('628xxx@s.whatsapp.net', imageBuffer, {
|
|
394
|
+
* pack: 'My Stickers',
|
|
395
|
+
* author: 'Bot'
|
|
396
|
+
* });
|
|
397
|
+
*/
|
|
220
398
|
async sendSticker(jid, buffer, options = {}) {
|
|
221
399
|
const stickerBuffer = await createSticker(buffer, options);
|
|
222
400
|
return await this.sendMessage(jid, {
|
|
@@ -225,14 +403,37 @@ export class Client extends EventEmitter {
|
|
|
225
403
|
});
|
|
226
404
|
}
|
|
227
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Send contact(s) message
|
|
408
|
+
* @async
|
|
409
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
410
|
+
* @param {Array<{displayName: string, vcard: string}>} contacts - Array of contact objects
|
|
411
|
+
* @param {Object} [options={}] - Additional options
|
|
412
|
+
* @returns {Promise<Object>} Sent message object
|
|
413
|
+
* @example
|
|
414
|
+
* await client.sendContact('628xxx@s.whatsapp.net', [{
|
|
415
|
+
* displayName: 'John Doe',
|
|
416
|
+
* vcard: 'BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nTEL:+1234567890\nEND:VCARD'
|
|
417
|
+
* }]);
|
|
418
|
+
*/
|
|
228
419
|
async sendContact(jid, contacts, options = {}) {
|
|
229
|
-
// contacts = [{ displayName, vcard }]
|
|
230
420
|
return await this.sendMessage(jid, {
|
|
231
421
|
contacts: { contacts },
|
|
232
422
|
...options
|
|
233
423
|
});
|
|
234
424
|
}
|
|
235
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Send location message
|
|
428
|
+
* @async
|
|
429
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
430
|
+
* @param {number} latitude - Location latitude
|
|
431
|
+
* @param {number} longitude - Location longitude
|
|
432
|
+
* @param {Object} [options={}] - Additional options
|
|
433
|
+
* @returns {Promise<Object>} Sent message object
|
|
434
|
+
* @example
|
|
435
|
+
* await client.sendLocation('628xxx@s.whatsapp.net', -6.200000, 106.816666);
|
|
436
|
+
*/
|
|
236
437
|
async sendLocation(jid, latitude, longitude, options = {}) {
|
|
237
438
|
return await this.sendMessage(jid, {
|
|
238
439
|
location: { degreesLatitude: latitude, degreesLongitude: longitude },
|
|
@@ -240,6 +441,18 @@ export class Client extends EventEmitter {
|
|
|
240
441
|
});
|
|
241
442
|
}
|
|
242
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Send poll message
|
|
446
|
+
* @async
|
|
447
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
448
|
+
* @param {string} name - Poll question
|
|
449
|
+
* @param {Array<string>} values - Poll options
|
|
450
|
+
* @param {Object} [options={}] - Additional options
|
|
451
|
+
* @param {number} [options.selectableCount=1] - Number of selectable options
|
|
452
|
+
* @returns {Promise<Object>} Sent message object
|
|
453
|
+
* @example
|
|
454
|
+
* await client.sendPoll('628xxx@s.whatsapp.net', 'Favorite color?', ['Red', 'Blue', 'Green']);
|
|
455
|
+
*/
|
|
243
456
|
async sendPoll(jid, name, values, options = {}) {
|
|
244
457
|
return await this.sendMessage(jid, {
|
|
245
458
|
poll: {
|
|
@@ -251,6 +464,16 @@ export class Client extends EventEmitter {
|
|
|
251
464
|
});
|
|
252
465
|
}
|
|
253
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Send reaction to a message
|
|
469
|
+
* @async
|
|
470
|
+
* @param {string} jid - WhatsApp JID (chat ID)
|
|
471
|
+
* @param {Object} messageKey - Message key object from the message to react to
|
|
472
|
+
* @param {string} emoji - Emoji to react with
|
|
473
|
+
* @returns {Promise<Object>} Sent reaction object
|
|
474
|
+
* @example
|
|
475
|
+
* await client.sendReact('628xxx@s.whatsapp.net', message.key, '👍');
|
|
476
|
+
*/
|
|
254
477
|
async sendReact(jid, messageKey, emoji) {
|
|
255
478
|
return await this.sendMessage(jid, {
|
|
256
479
|
react: { text: emoji, key: messageKey }
|
|
@@ -259,8 +482,15 @@ export class Client extends EventEmitter {
|
|
|
259
482
|
|
|
260
483
|
/**
|
|
261
484
|
* Read and download view once message
|
|
485
|
+
* @async
|
|
262
486
|
* @param {Object} quotedMessage - The quoted message object (m.quoted)
|
|
263
|
-
* @returns {Promise<{buffer: Buffer, type: 'image'|'video', caption: string}>}
|
|
487
|
+
* @returns {Promise<{buffer: Buffer, type: 'image'|'video', caption: string}>} Object containing media buffer, type, and caption
|
|
488
|
+
* @throws {Error} If quoted message is not provided or not a view once message
|
|
489
|
+
* @example
|
|
490
|
+
* const { buffer, type, caption } = await client.readViewOnce(m.quoted);
|
|
491
|
+
* if (type === 'image') {
|
|
492
|
+
* await client.sendImage(m.from, buffer, caption);
|
|
493
|
+
* }
|
|
264
494
|
*/
|
|
265
495
|
async readViewOnce(quotedMessage) {
|
|
266
496
|
if (!quotedMessage) {
|
|
@@ -294,10 +524,15 @@ export class Client extends EventEmitter {
|
|
|
294
524
|
|
|
295
525
|
/**
|
|
296
526
|
* Read view once message and send to chat
|
|
297
|
-
* @
|
|
527
|
+
* @async
|
|
528
|
+
* @param {string} jid - WhatsApp JID (chat ID) to send to
|
|
298
529
|
* @param {Object} quotedMessage - The quoted message object (m.quoted)
|
|
299
|
-
* @param {Object} options - Additional options
|
|
300
|
-
* @returns {Promise}
|
|
530
|
+
* @param {Object} [options={}] - Additional options
|
|
531
|
+
* @returns {Promise<Object>} Sent message object
|
|
532
|
+
* @throws {Error} If reading or sending view once message fails
|
|
533
|
+
* @example
|
|
534
|
+
* // Forward a view once message
|
|
535
|
+
* await client.sendViewOnce(m.from, m.quoted);
|
|
301
536
|
*/
|
|
302
537
|
async sendViewOnce(jid, quotedMessage, options = {}) {
|
|
303
538
|
try {
|
|
@@ -321,34 +556,102 @@ export class Client extends EventEmitter {
|
|
|
321
556
|
}
|
|
322
557
|
}
|
|
323
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Get group metadata
|
|
561
|
+
* @async
|
|
562
|
+
* @param {string} jid - Group JID (format: groupId@g.us)
|
|
563
|
+
* @returns {Promise<Object>} Group metadata object containing id, subject, participants, etc.
|
|
564
|
+
* @example
|
|
565
|
+
* const metadata = await client.groupMetadata('groupId@g.us');
|
|
566
|
+
* console.log(metadata.subject); // Group name
|
|
567
|
+
*/
|
|
324
568
|
async groupMetadata(jid) {
|
|
325
569
|
return await this.sock.groupMetadata(jid);
|
|
326
570
|
}
|
|
327
571
|
|
|
572
|
+
/**
|
|
573
|
+
* Update group participants (add, remove, promote, demote)
|
|
574
|
+
* @async
|
|
575
|
+
* @param {string} jid - Group JID (format: groupId@g.us)
|
|
576
|
+
* @param {Array<string>} participants - Array of participant JIDs
|
|
577
|
+
* @param {'add'|'remove'|'promote'|'demote'} action - Action to perform
|
|
578
|
+
* @returns {Promise<Object>} Update result
|
|
579
|
+
* @example
|
|
580
|
+
* // Remove participants
|
|
581
|
+
* await client.groupParticipantsUpdate('groupId@g.us', ['628xxx@s.whatsapp.net'], 'remove');
|
|
582
|
+
* // Promote to admin
|
|
583
|
+
* await client.groupParticipantsUpdate('groupId@g.us', ['628xxx@s.whatsapp.net'], 'promote');
|
|
584
|
+
*/
|
|
328
585
|
async groupParticipantsUpdate(jid, participants, action) {
|
|
329
586
|
return await this.sock.groupParticipantsUpdate(jid, participants, action);
|
|
330
587
|
}
|
|
331
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Update group subject (name)
|
|
591
|
+
* @async
|
|
592
|
+
* @param {string} jid - Group JID (format: groupId@g.us)
|
|
593
|
+
* @param {string} subject - New group name
|
|
594
|
+
* @returns {Promise<Object>} Update result
|
|
595
|
+
* @example
|
|
596
|
+
* await client.groupUpdateSubject('groupId@g.us', 'My New Group Name');
|
|
597
|
+
*/
|
|
332
598
|
async groupUpdateSubject(jid, subject) {
|
|
333
599
|
return await this.sock.groupUpdateSubject(jid, subject);
|
|
334
600
|
}
|
|
335
601
|
|
|
602
|
+
/**
|
|
603
|
+
* Update group description
|
|
604
|
+
* @async
|
|
605
|
+
* @param {string} jid - Group JID (format: groupId@g.us)
|
|
606
|
+
* @param {string} description - New group description
|
|
607
|
+
* @returns {Promise<Object>} Update result
|
|
608
|
+
* @example
|
|
609
|
+
* await client.groupUpdateDescription('groupId@g.us', 'This is my group description');
|
|
610
|
+
*/
|
|
336
611
|
async groupUpdateDescription(jid, description) {
|
|
337
612
|
return await this.sock.groupUpdateDescription(jid, description);
|
|
338
613
|
}
|
|
339
614
|
|
|
615
|
+
/**
|
|
616
|
+
* Load a single plugin from file path
|
|
617
|
+
* @async
|
|
618
|
+
* @param {string} path - Path to plugin file
|
|
619
|
+
* @returns {Promise<void>}
|
|
620
|
+
* @example
|
|
621
|
+
* await client.loadPlugin('./plugins/my-plugin.js');
|
|
622
|
+
*/
|
|
340
623
|
async loadPlugin(path) {
|
|
341
624
|
await this.pluginHandler.load(path);
|
|
342
625
|
}
|
|
343
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Load all plugins from a directory
|
|
629
|
+
* @async
|
|
630
|
+
* @param {string} directory - Path to plugins directory
|
|
631
|
+
* @returns {Promise<void>}
|
|
632
|
+
* @example
|
|
633
|
+
* await client.loadPlugins('./plugins');
|
|
634
|
+
*/
|
|
344
635
|
async loadPlugins(directory) {
|
|
345
636
|
await this.pluginHandler.loadAll(directory);
|
|
346
637
|
}
|
|
347
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Get command prefix
|
|
641
|
+
* @returns {string} Current command prefix
|
|
642
|
+
* @example
|
|
643
|
+
* console.log(client.prefix); // '!'
|
|
644
|
+
*/
|
|
348
645
|
get prefix() {
|
|
349
646
|
return this.config.prefix || '!';
|
|
350
647
|
}
|
|
351
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Set command prefix
|
|
651
|
+
* @param {string} prefix - New command prefix
|
|
652
|
+
* @example
|
|
653
|
+
* client.prefix = '.';
|
|
654
|
+
*/
|
|
352
655
|
set prefix(prefix) {
|
|
353
656
|
this.config.prefix = prefix;
|
|
354
657
|
}
|
|
@@ -2,7 +2,42 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} Plugin
|
|
7
|
+
* @property {string} name - Plugin name
|
|
8
|
+
* @property {Array<string>} commands - Command aliases
|
|
9
|
+
* @property {string} [category] - Plugin category
|
|
10
|
+
* @property {string} [description] - Plugin description
|
|
11
|
+
* @property {boolean} [owner=false] - Owner only command
|
|
12
|
+
* @property {boolean} [group=false] - Group only command
|
|
13
|
+
* @property {boolean} [private=false] - Private chat only command
|
|
14
|
+
* @property {boolean} [admin=false] - Admin only command
|
|
15
|
+
* @property {boolean} [botAdmin=false] - Requires bot to be admin
|
|
16
|
+
* @property {Function} exec - Plugin execution function
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} PluginContext
|
|
21
|
+
* @property {import('../client/Client.js').Client} client - Bot client instance
|
|
22
|
+
* @property {Object} m - Serialized message object
|
|
23
|
+
* @property {Array<string>} args - Command arguments
|
|
24
|
+
* @property {string} command - Command name that was used
|
|
25
|
+
* @property {string} prefix - Command prefix
|
|
26
|
+
* @property {Object} sock - Raw WhatsApp socket
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Plugin handler for loading and executing bot plugins
|
|
31
|
+
* @class PluginHandler
|
|
32
|
+
* @example
|
|
33
|
+
* const handler = new PluginHandler(client);
|
|
34
|
+
* await handler.loadAll('./plugins');
|
|
35
|
+
*/
|
|
5
36
|
export class PluginHandler {
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new PluginHandler instance
|
|
39
|
+
* @param {import('../client/Client.js').Client} client - Bot client instance
|
|
40
|
+
*/
|
|
6
41
|
constructor(client) {
|
|
7
42
|
this.client = client;
|
|
8
43
|
this.plugins = new Map();
|
|
@@ -10,6 +45,18 @@ export class PluginHandler {
|
|
|
10
45
|
this.isLoaded = false;
|
|
11
46
|
}
|
|
12
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Load a single plugin from file path
|
|
50
|
+
* @async
|
|
51
|
+
* @param {string} pluginPath - Absolute path to plugin file
|
|
52
|
+
* @returns {Promise<Plugin|null>} Loaded plugin object or null if failed
|
|
53
|
+
* @throws {Error} If plugin is invalid
|
|
54
|
+
* @example
|
|
55
|
+
* const plugin = await handler.load('/path/to/plugin.js');
|
|
56
|
+
* if (plugin) {
|
|
57
|
+
* console.log('Loaded:', plugin.name);
|
|
58
|
+
* }
|
|
59
|
+
*/
|
|
13
60
|
async load(pluginPath) {
|
|
14
61
|
try {
|
|
15
62
|
const module = await import(`file://${pluginPath}?update=${Date.now()}`);
|
|
@@ -55,6 +102,15 @@ export class PluginHandler {
|
|
|
55
102
|
}
|
|
56
103
|
}
|
|
57
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Load all plugins from a directory recursively
|
|
107
|
+
* @async
|
|
108
|
+
* @param {string} directory - Path to plugins directory
|
|
109
|
+
* @returns {Promise<number>} Number of successfully loaded plugins
|
|
110
|
+
* @example
|
|
111
|
+
* const count = await handler.loadAll('./plugins');
|
|
112
|
+
* console.log(`Loaded ${count} plugins`);
|
|
113
|
+
*/
|
|
58
114
|
async loadAll(directory) {
|
|
59
115
|
if (!fs.existsSync(directory)) {
|
|
60
116
|
console.warn(`Plugin directory not found: ${directory}`);
|
|
@@ -75,6 +131,13 @@ export class PluginHandler {
|
|
|
75
131
|
return loaded;
|
|
76
132
|
}
|
|
77
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Find all files with specific extension in directory recursively
|
|
136
|
+
* @private
|
|
137
|
+
* @param {string} dir - Directory to search
|
|
138
|
+
* @param {string} ext - File extension (e.g., '.js')
|
|
139
|
+
* @returns {Array<string>} Array of absolute file paths
|
|
140
|
+
*/
|
|
78
141
|
findFiles(dir, ext) {
|
|
79
142
|
let results = [];
|
|
80
143
|
const list = fs.readdirSync(dir);
|
|
@@ -93,6 +156,17 @@ export class PluginHandler {
|
|
|
93
156
|
return results;
|
|
94
157
|
}
|
|
95
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Execute plugin command from message
|
|
161
|
+
* @async
|
|
162
|
+
* @param {Object} m - Serialized message object
|
|
163
|
+
* @returns {Promise<void>}
|
|
164
|
+
* @example
|
|
165
|
+
* // This is automatically called by Client when message starts with prefix
|
|
166
|
+
* client.on('message', async (m) => {
|
|
167
|
+
* await handler.execute(m);
|
|
168
|
+
* });
|
|
169
|
+
*/
|
|
96
170
|
async execute(m) {
|
|
97
171
|
if (!m.body) return;
|
|
98
172
|
|
|
@@ -158,6 +232,15 @@ export class PluginHandler {
|
|
|
158
232
|
}
|
|
159
233
|
}
|
|
160
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Check if user is bot owner
|
|
237
|
+
* @param {string} jid - User JID to check
|
|
238
|
+
* @returns {boolean} True if user is owner
|
|
239
|
+
* @example
|
|
240
|
+
* if (handler.isOwner(m.sender)) {
|
|
241
|
+
* // Owner-only logic
|
|
242
|
+
* }
|
|
243
|
+
*/
|
|
161
244
|
isOwner(jid) {
|
|
162
245
|
const owners = this.client.config.owners || this.client.config.owner || [];
|
|
163
246
|
const ownerList = Array.isArray(owners) ? owners : [owners];
|
|
@@ -165,6 +248,16 @@ export class PluginHandler {
|
|
|
165
248
|
return ownerList.includes(number) || ownerList.includes(jid);
|
|
166
249
|
}
|
|
167
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Reload/unload a plugin by name
|
|
253
|
+
* @param {string} pluginName - Name of plugin to reload
|
|
254
|
+
* @returns {boolean} True if plugin was found and reloaded
|
|
255
|
+
* @example
|
|
256
|
+
* if (handler.reload('ping')) {
|
|
257
|
+
* await handler.load('./plugins/ping.js');
|
|
258
|
+
* console.log('Plugin reloaded');
|
|
259
|
+
* }
|
|
260
|
+
*/
|
|
168
261
|
reload(pluginName) {
|
|
169
262
|
const plugin = this.plugins.get(pluginName);
|
|
170
263
|
if (!plugin) return false;
|
|
@@ -178,10 +271,27 @@ export class PluginHandler {
|
|
|
178
271
|
return true;
|
|
179
272
|
}
|
|
180
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Get list of all loaded plugins
|
|
276
|
+
* @returns {Array<Plugin>} Array of plugin objects
|
|
277
|
+
* @example
|
|
278
|
+
* const plugins = handler.list();
|
|
279
|
+
* plugins.forEach(p => console.log(p.name));
|
|
280
|
+
*/
|
|
181
281
|
list() {
|
|
182
282
|
return Array.from(this.plugins.values());
|
|
183
283
|
}
|
|
184
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Get a specific plugin by name
|
|
287
|
+
* @param {string} name - Plugin name
|
|
288
|
+
* @returns {Plugin|undefined} Plugin object or undefined if not found
|
|
289
|
+
* @example
|
|
290
|
+
* const pingPlugin = handler.get('ping');
|
|
291
|
+
* if (pingPlugin) {
|
|
292
|
+
* console.log('Commands:', pingPlugin.commands);
|
|
293
|
+
* }
|
|
294
|
+
*/
|
|
185
295
|
get(name) {
|
|
186
296
|
return this.plugins.get(name);
|
|
187
297
|
}
|