@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.
@@ -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
- // Request pairing code after socket is initialized
82
- setTimeout(async () => {
83
- try {
84
- const code = await this.sock.requestPairingCode(phoneNumber);
85
- this.emit('pairing.code', code);
86
-
87
- // Log to console with formatting
88
- console.log('\n┌─────────────────────────────────────┐');
89
- console.log('│ WhatsApp Pairing Code │');
90
- console.log('├─────────────────────────────────────┤');
91
- console.log(`│ Code: ${code} │`);
92
- console.log('└─────────────────────────────────────┘');
93
- console.log('\nSteps to pair:');
94
- console.log('1. Open WhatsApp on your phone');
95
- console.log('2. Go to Settings > Linked Devices');
96
- console.log('3. Tap "Link a Device"');
97
- console.log('4. Enter the code above\n');
98
- } catch (error) {
99
- this.emit('pairing.error', error);
100
- console.error('Failed to request pairing code:', error.message);
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
- }, 3000);
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
- // Helper methods
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
- * @param {string} jid - Chat ID to send to
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
  }