@roidev/kachina-md 2.0.5 → 2.1.1

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.
@@ -2,7 +2,8 @@ import makeWASocket, {
2
2
  DisconnectReason,
3
3
  useMultiFileAuthState,
4
4
  makeCacheableSignalKeyStore,
5
- fetchLatestBaileysVersion
5
+ fetchLatestBaileysVersion,
6
+ downloadMediaMessage
6
7
  } from 'baileys';
7
8
  import { Boom } from '@hapi/boom';
8
9
  import pino from 'pino';
@@ -15,9 +16,56 @@ import {
15
16
  StickerTypes
16
17
  } from '../helpers/sticker.js';
17
18
 
19
+ /**
20
+ * @typedef {Object} ClientOptions
21
+ * @property {string} [sessionId='kachina-session'] - Session ID for storing authentication data
22
+ * @property {string} [phoneNumber=''] - Phone number for pairing method (format: 628123456789)
23
+ * @property {'qr'|'pairing'} [loginMethod='qr'] - Login method: 'qr' for QR code, 'pairing' for pairing code
24
+ * @property {Array<string>} [browser=['Kachina-MD', 'Chrome', '1.0.0']] - Browser identification
25
+ * @property {Object} [logger] - Pino logger instance
26
+ * @property {string} [prefix='!'] - Command prefix for plugin system
27
+ * @property {Object} [store] - Optional message store for caching
28
+ */
29
+
30
+ /**
31
+ * Main WhatsApp bot client class
32
+ *
33
+ * @class Client
34
+ * @extends EventEmitter
35
+ *
36
+ * @fires Client#ready - Emitted when bot is connected and ready
37
+ * @fires Client#message - Emitted when a new message is received
38
+ * @fires Client#group.update - Emitted when group participants are updated
39
+ * @fires Client#groups.update - Emitted when group info is updated
40
+ * @fires Client#call - Emitted when receiving a call
41
+ * @fires Client#pairing.code - Emitted when pairing code is generated (pairing mode only)
42
+ * @fires Client#pairing.error - Emitted when pairing code request fails
43
+ * @fires Client#reconnecting - Emitted when bot is reconnecting
44
+ * @fires Client#connecting - Emitted when bot is connecting
45
+ * @fires Client#logout - Emitted when bot is logged out
46
+ *
47
+ * @example
48
+ * // QR Code login
49
+ * const client = new Client({
50
+ * sessionId: 'my-bot',
51
+ * loginMethod: 'qr'
52
+ * });
53
+ *
54
+ * @example
55
+ * // Pairing code login
56
+ * const client = new Client({
57
+ * sessionId: 'my-bot',
58
+ * phoneNumber: '628123456789',
59
+ * loginMethod: 'pairing'
60
+ * });
61
+ */
18
62
  export class Client extends EventEmitter {
19
63
  static StickerTypes = StickerTypes;
20
64
 
65
+ /**
66
+ * Creates a new Client instance
67
+ * @param {ClientOptions} [options={}] - Client configuration options
68
+ */
21
69
  constructor(options = {}) {
22
70
  super();
23
71
 
@@ -36,6 +84,18 @@ export class Client extends EventEmitter {
36
84
  this.pluginHandler = new PluginHandler(this);
37
85
  }
38
86
 
87
+ /**
88
+ * Start the WhatsApp bot connection
89
+ * Initializes the socket connection and handles authentication
90
+ * @async
91
+ * @returns {Promise<Object>} The initialized socket connection
92
+ * @throws {Error} If phone number is invalid (pairing mode)
93
+ * @example
94
+ * await client.start();
95
+ * client.on('ready', (user) => {
96
+ * console.log('Bot ready:', user.id);
97
+ * });
98
+ */
39
99
  async start() {
40
100
  const { state, saveCreds } = await useMultiFileAuthState(this.config.sessionId);
41
101
  const { version } = await fetchLatestBaileysVersion();
@@ -134,6 +194,15 @@ export class Client extends EventEmitter {
134
194
  return this.sock;
135
195
  }
136
196
 
197
+ /**
198
+ * Handle connection updates from WhatsApp socket
199
+ * @private
200
+ * @async
201
+ * @param {Object} update - Connection update object
202
+ * @param {string} [update.connection] - Connection status: 'open', 'close', 'connecting'
203
+ * @param {Object} [update.lastDisconnect] - Last disconnect information
204
+ * @param {string} [update.qr] - QR code data for scanning
205
+ */
137
206
  async handleConnectionUpdate(update) {
138
207
  const { connection, lastDisconnect, qr } = update;
139
208
 
@@ -169,15 +238,45 @@ export class Client extends EventEmitter {
169
238
  }
170
239
  }
171
240
 
172
- // Helper methods
241
+ /**
242
+ * Send a raw message to WhatsApp
243
+ * @async
244
+ * @param {string} jid - WhatsApp JID (chat ID) - format: number@s.whatsapp.net or groupId@g.us
245
+ * @param {Object} content - Message content object (text, image, video, etc.)
246
+ * @param {Object} [options={}] - Additional options for message
247
+ * @returns {Promise<Object>} Sent message object
248
+ * @example
249
+ * await client.sendMessage('628xxx@s.whatsapp.net', { text: 'Hello' });
250
+ */
173
251
  async sendMessage(jid, content, options = {}) {
174
252
  return await this.sock.sendMessage(jid, content, options);
175
253
  }
176
254
 
255
+ /**
256
+ * Send a text message
257
+ * @async
258
+ * @param {string} jid - WhatsApp JID (chat ID)
259
+ * @param {string} text - Text message content
260
+ * @param {Object} [options={}] - Additional options
261
+ * @returns {Promise<Object>} Sent message object
262
+ * @example
263
+ * await client.sendText('628xxx@s.whatsapp.net', 'Hello World!');
264
+ */
177
265
  async sendText(jid, text, options = {}) {
178
266
  return await this.sendMessage(jid, { text }, options);
179
267
  }
180
268
 
269
+ /**
270
+ * Send an image message
271
+ * @async
272
+ * @param {string} jid - WhatsApp JID (chat ID)
273
+ * @param {Buffer|string} buffer - Image buffer or file path/URL
274
+ * @param {string} [caption=''] - Image caption
275
+ * @param {Object} [options={}] - Additional options
276
+ * @returns {Promise<Object>} Sent message object
277
+ * @example
278
+ * await client.sendImage('628xxx@s.whatsapp.net', imageBuffer, 'My Image');
279
+ */
181
280
  async sendImage(jid, buffer, caption = '', options = {}) {
182
281
  const content = {
183
282
  image: buffer,
@@ -187,6 +286,17 @@ export class Client extends EventEmitter {
187
286
  return await this.sendMessage(jid, content, options);
188
287
  }
189
288
 
289
+ /**
290
+ * Send a video message
291
+ * @async
292
+ * @param {string} jid - WhatsApp JID (chat ID)
293
+ * @param {Buffer|string} buffer - Video buffer or file path/URL
294
+ * @param {string} [caption=''] - Video caption
295
+ * @param {Object} [options={}] - Additional options
296
+ * @returns {Promise<Object>} Sent message object
297
+ * @example
298
+ * await client.sendVideo('628xxx@s.whatsapp.net', videoBuffer, 'My Video');
299
+ */
190
300
  async sendVideo(jid, buffer, caption = '', options = {}) {
191
301
  const content = {
192
302
  video: buffer,
@@ -196,6 +306,21 @@ export class Client extends EventEmitter {
196
306
  return await this.sendMessage(jid, content, options);
197
307
  }
198
308
 
309
+ /**
310
+ * Send an audio message
311
+ * @async
312
+ * @param {string} jid - WhatsApp JID (chat ID)
313
+ * @param {Buffer|string} buffer - Audio buffer or file path/URL
314
+ * @param {Object} [options={}] - Additional options
315
+ * @param {string} [options.mimetype='audio/mp4'] - Audio mime type
316
+ * @param {boolean} [options.ptt=false] - Push to talk (voice note)
317
+ * @returns {Promise<Object>} Sent message object
318
+ * @example
319
+ * // Send as audio file
320
+ * await client.sendAudio('628xxx@s.whatsapp.net', audioBuffer);
321
+ * // Send as voice note
322
+ * await client.sendAudio('628xxx@s.whatsapp.net', audioBuffer, { ptt: true });
323
+ */
199
324
  async sendAudio(jid, buffer, options = {}) {
200
325
  const content = {
201
326
  audio: buffer,
@@ -206,6 +331,18 @@ export class Client extends EventEmitter {
206
331
  return await this.sendMessage(jid, content, options);
207
332
  }
208
333
 
334
+ /**
335
+ * Send a document message
336
+ * @async
337
+ * @param {string} jid - WhatsApp JID (chat ID)
338
+ * @param {Buffer|string} buffer - Document buffer or file path/URL
339
+ * @param {string} filename - Document filename with extension
340
+ * @param {string} mimetype - Document mime type (e.g., 'application/pdf')
341
+ * @param {Object} [options={}] - Additional options
342
+ * @returns {Promise<Object>} Sent message object
343
+ * @example
344
+ * await client.sendDocument('628xxx@s.whatsapp.net', pdfBuffer, 'file.pdf', 'application/pdf');
345
+ */
209
346
  async sendDocument(jid, buffer, filename, mimetype, options = {}) {
210
347
  return await this.sendMessage(jid, {
211
348
  document: buffer,
@@ -215,7 +352,22 @@ export class Client extends EventEmitter {
215
352
  });
216
353
  }
217
354
 
218
-
355
+ /**
356
+ * Send a sticker message
357
+ * @async
358
+ * @param {string} jid - WhatsApp JID (chat ID)
359
+ * @param {Buffer|string} buffer - Image buffer or file path/URL to convert to sticker
360
+ * @param {Object} [options={}] - Sticker options
361
+ * @param {string} [options.pack] - Sticker pack name
362
+ * @param {string} [options.author] - Sticker author name
363
+ * @param {string} [options.type] - Sticker type from Client.StickerTypes
364
+ * @returns {Promise<Object>} Sent message object
365
+ * @example
366
+ * await client.sendSticker('628xxx@s.whatsapp.net', imageBuffer, {
367
+ * pack: 'My Stickers',
368
+ * author: 'Bot'
369
+ * });
370
+ */
219
371
  async sendSticker(jid, buffer, options = {}) {
220
372
  const stickerBuffer = await createSticker(buffer, options);
221
373
  return await this.sendMessage(jid, {
@@ -224,14 +376,37 @@ export class Client extends EventEmitter {
224
376
  });
225
377
  }
226
378
 
379
+ /**
380
+ * Send contact(s) message
381
+ * @async
382
+ * @param {string} jid - WhatsApp JID (chat ID)
383
+ * @param {Array<{displayName: string, vcard: string}>} contacts - Array of contact objects
384
+ * @param {Object} [options={}] - Additional options
385
+ * @returns {Promise<Object>} Sent message object
386
+ * @example
387
+ * await client.sendContact('628xxx@s.whatsapp.net', [{
388
+ * displayName: 'John Doe',
389
+ * vcard: 'BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nTEL:+1234567890\nEND:VCARD'
390
+ * }]);
391
+ */
227
392
  async sendContact(jid, contacts, options = {}) {
228
- // contacts = [{ displayName, vcard }]
229
393
  return await this.sendMessage(jid, {
230
394
  contacts: { contacts },
231
395
  ...options
232
396
  });
233
397
  }
234
398
 
399
+ /**
400
+ * Send location message
401
+ * @async
402
+ * @param {string} jid - WhatsApp JID (chat ID)
403
+ * @param {number} latitude - Location latitude
404
+ * @param {number} longitude - Location longitude
405
+ * @param {Object} [options={}] - Additional options
406
+ * @returns {Promise<Object>} Sent message object
407
+ * @example
408
+ * await client.sendLocation('628xxx@s.whatsapp.net', -6.200000, 106.816666);
409
+ */
235
410
  async sendLocation(jid, latitude, longitude, options = {}) {
236
411
  return await this.sendMessage(jid, {
237
412
  location: { degreesLatitude: latitude, degreesLongitude: longitude },
@@ -239,6 +414,18 @@ export class Client extends EventEmitter {
239
414
  });
240
415
  }
241
416
 
417
+ /**
418
+ * Send poll message
419
+ * @async
420
+ * @param {string} jid - WhatsApp JID (chat ID)
421
+ * @param {string} name - Poll question
422
+ * @param {Array<string>} values - Poll options
423
+ * @param {Object} [options={}] - Additional options
424
+ * @param {number} [options.selectableCount=1] - Number of selectable options
425
+ * @returns {Promise<Object>} Sent message object
426
+ * @example
427
+ * await client.sendPoll('628xxx@s.whatsapp.net', 'Favorite color?', ['Red', 'Blue', 'Green']);
428
+ */
242
429
  async sendPoll(jid, name, values, options = {}) {
243
430
  return await this.sendMessage(jid, {
244
431
  poll: {
@@ -250,40 +437,194 @@ export class Client extends EventEmitter {
250
437
  });
251
438
  }
252
439
 
440
+ /**
441
+ * Send reaction to a message
442
+ * @async
443
+ * @param {string} jid - WhatsApp JID (chat ID)
444
+ * @param {Object} messageKey - Message key object from the message to react to
445
+ * @param {string} emoji - Emoji to react with
446
+ * @returns {Promise<Object>} Sent reaction object
447
+ * @example
448
+ * await client.sendReact('628xxx@s.whatsapp.net', message.key, '👍');
449
+ */
253
450
  async sendReact(jid, messageKey, emoji) {
254
451
  return await this.sendMessage(jid, {
255
452
  react: { text: emoji, key: messageKey }
256
453
  });
257
454
  }
258
455
 
456
+ /**
457
+ * Read and download view once message
458
+ * @async
459
+ * @param {Object} quotedMessage - The quoted message object (m.quoted)
460
+ * @returns {Promise<{buffer: Buffer, type: 'image'|'video', caption: string}>} Object containing media buffer, type, and caption
461
+ * @throws {Error} If quoted message is not provided or not a view once message
462
+ * @example
463
+ * const { buffer, type, caption } = await client.readViewOnce(m.quoted);
464
+ * if (type === 'image') {
465
+ * await client.sendImage(m.from, buffer, caption);
466
+ * }
467
+ */
468
+ async readViewOnce(quotedMessage) {
469
+ if (!quotedMessage) {
470
+ throw new Error('Quoted message is required');
471
+ }
472
+
473
+ // Get view once message
474
+ const ViewOnceImg = quotedMessage?.message?.imageMessage;
475
+ const ViewOnceVid = quotedMessage?.message?.videoMessage;
476
+
477
+ // Check if it's a view once message
478
+ if (!ViewOnceImg?.viewOnce && !ViewOnceVid?.viewOnce) {
479
+ throw new Error('Message is not a view once message');
480
+ }
481
+
482
+ // Download the media
483
+ const buffer = await downloadMediaMessage(
484
+ quotedMessage,
485
+ 'buffer',
486
+ {},
487
+ { logger: this.config.logger }
488
+ );
489
+
490
+ // Return buffer with type and caption
491
+ return {
492
+ buffer,
493
+ type: ViewOnceImg ? 'image' : 'video',
494
+ caption: ViewOnceImg?.caption || ViewOnceVid?.caption || ''
495
+ };
496
+ }
497
+
498
+ /**
499
+ * Read view once message and send to chat
500
+ * @async
501
+ * @param {string} jid - WhatsApp JID (chat ID) to send to
502
+ * @param {Object} quotedMessage - The quoted message object (m.quoted)
503
+ * @param {Object} [options={}] - Additional options
504
+ * @returns {Promise<Object>} Sent message object
505
+ * @throws {Error} If reading or sending view once message fails
506
+ * @example
507
+ * // Forward a view once message
508
+ * await client.sendViewOnce(m.from, m.quoted);
509
+ */
510
+ async sendViewOnce(jid, quotedMessage, options = {}) {
511
+ try {
512
+ // Read the view once message
513
+ const { buffer, type, caption } = await this.readViewOnce(quotedMessage);
514
+
515
+ // Send based on type
516
+ if (type === 'image') {
517
+ return await this.sendImage(jid, buffer, caption, {
518
+ jpegThumbnail: null,
519
+ ...options
520
+ });
521
+ } else {
522
+ return await this.sendVideo(jid, buffer, caption, {
523
+ jpegThumbnail: null,
524
+ ...options
525
+ });
526
+ }
527
+ } catch (error) {
528
+ throw new Error(`Failed to send view once: ${error.message}`);
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Get group metadata
534
+ * @async
535
+ * @param {string} jid - Group JID (format: groupId@g.us)
536
+ * @returns {Promise<Object>} Group metadata object containing id, subject, participants, etc.
537
+ * @example
538
+ * const metadata = await client.groupMetadata('groupId@g.us');
539
+ * console.log(metadata.subject); // Group name
540
+ */
259
541
  async groupMetadata(jid) {
260
542
  return await this.sock.groupMetadata(jid);
261
543
  }
262
544
 
545
+ /**
546
+ * Update group participants (add, remove, promote, demote)
547
+ * @async
548
+ * @param {string} jid - Group JID (format: groupId@g.us)
549
+ * @param {Array<string>} participants - Array of participant JIDs
550
+ * @param {'add'|'remove'|'promote'|'demote'} action - Action to perform
551
+ * @returns {Promise<Object>} Update result
552
+ * @example
553
+ * // Remove participants
554
+ * await client.groupParticipantsUpdate('groupId@g.us', ['628xxx@s.whatsapp.net'], 'remove');
555
+ * // Promote to admin
556
+ * await client.groupParticipantsUpdate('groupId@g.us', ['628xxx@s.whatsapp.net'], 'promote');
557
+ */
263
558
  async groupParticipantsUpdate(jid, participants, action) {
264
559
  return await this.sock.groupParticipantsUpdate(jid, participants, action);
265
560
  }
266
561
 
562
+ /**
563
+ * Update group subject (name)
564
+ * @async
565
+ * @param {string} jid - Group JID (format: groupId@g.us)
566
+ * @param {string} subject - New group name
567
+ * @returns {Promise<Object>} Update result
568
+ * @example
569
+ * await client.groupUpdateSubject('groupId@g.us', 'My New Group Name');
570
+ */
267
571
  async groupUpdateSubject(jid, subject) {
268
572
  return await this.sock.groupUpdateSubject(jid, subject);
269
573
  }
270
574
 
575
+ /**
576
+ * Update group description
577
+ * @async
578
+ * @param {string} jid - Group JID (format: groupId@g.us)
579
+ * @param {string} description - New group description
580
+ * @returns {Promise<Object>} Update result
581
+ * @example
582
+ * await client.groupUpdateDescription('groupId@g.us', 'This is my group description');
583
+ */
271
584
  async groupUpdateDescription(jid, description) {
272
585
  return await this.sock.groupUpdateDescription(jid, description);
273
586
  }
274
587
 
588
+ /**
589
+ * Load a single plugin from file path
590
+ * @async
591
+ * @param {string} path - Path to plugin file
592
+ * @returns {Promise<void>}
593
+ * @example
594
+ * await client.loadPlugin('./plugins/my-plugin.js');
595
+ */
275
596
  async loadPlugin(path) {
276
597
  await this.pluginHandler.load(path);
277
598
  }
278
599
 
600
+ /**
601
+ * Load all plugins from a directory
602
+ * @async
603
+ * @param {string} directory - Path to plugins directory
604
+ * @returns {Promise<void>}
605
+ * @example
606
+ * await client.loadPlugins('./plugins');
607
+ */
279
608
  async loadPlugins(directory) {
280
609
  await this.pluginHandler.loadAll(directory);
281
610
  }
282
611
 
612
+ /**
613
+ * Get command prefix
614
+ * @returns {string} Current command prefix
615
+ * @example
616
+ * console.log(client.prefix); // '!'
617
+ */
283
618
  get prefix() {
284
619
  return this.config.prefix || '!';
285
620
  }
286
621
 
622
+ /**
623
+ * Set command prefix
624
+ * @param {string} prefix - New command prefix
625
+ * @example
626
+ * client.prefix = '.';
627
+ */
287
628
  set prefix(prefix) {
288
629
  this.config.prefix = prefix;
289
630
  }
@@ -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
  }