@innovatorssoft/innovators-bot2 2.0.4 → 2.0.5

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/README.md CHANGED
@@ -308,6 +308,12 @@ await client.updateGroupsAddPrivacy('contacts')
308
308
  // Update default disappearing mode for new chats
309
309
  // Options: 0 (off), 86400 (24h), 604800 (7d), 7776000 (90d)
310
310
  await client.updateDefaultDisappearingMode(604800)
311
+
312
+ // Update profile status
313
+ await client.updateProfileStatus('Hello World!')
314
+
315
+ // Update profile name
316
+ await client.updateProfileName('My name')
311
317
  ```
312
318
 
313
319
  ### 7. Interactive Messages
@@ -403,10 +409,85 @@ await client.SendList('1234567890@s.whatsapp.net', {
403
409
  });
404
410
  ```
405
411
 
406
- ### 8. Message History
412
+ #### Interactive Messages (V2)
413
+
414
+ Modern interactive message generation with simplified API.
415
+
416
+ **Quick Reply Buttons (V2)**
417
+ ```javascript
418
+ await client.sendQuickReplyV2(jid, 'Please select an option below:', [
419
+ { id: 'btn-1', displayText: '✅ Accept' },
420
+ { id: 'btn-2', displayText: '❌ Reject' }
421
+ ], { footer: 'Powered by Innovators Soft' });
422
+ ```
423
+
424
+ **URL Button (V2)**
425
+ ```javascript
426
+ await client.sendUrlButtonV2(jid, 'Visit our website for more info', [
427
+ { displayText: '🌐 Open Website', url: 'https://example.com' }
428
+ ], { title: 'Product Info', footer: 'Click to open' });
429
+ ```
430
+
431
+ **Copy Code Button (V2)**
432
+ ```javascript
433
+ await client.sendCopyCodeV2(jid, 'Your OTP Code is:', '123456', '📋 Copy Code');
434
+ ```
435
+
436
+ **Combined Buttons (Mix URL, Reply, Copy, Call) (V2)**
437
+ ```javascript
438
+ await client.sendCombinedButtonsV2(jid, 'Choose an action:', [
439
+ { type: 'reply', displayText: '🛒 Order Now', id: 'order' },
440
+ { type: 'url', displayText: '🌐 Website', url: 'https://example.com' },
441
+ { type: 'call', displayText: '📞 Phone', phoneNumber: '+923224559543' },
442
+ { type: 'copy', displayText: '📋 Copy Promo', copyCode: 'PROMO2024' }
443
+ ], { title: 'Main Menu', footer: 'Innovators Soft' });
444
+ ```
445
+
446
+ **List Message (V2)**
447
+ ```javascript
448
+ await client.sendListV2(jid, {
449
+ title: '📋 Product Menu',
450
+ buttonText: 'View Menu',
451
+ description: 'Please select a product',
452
+ footer: 'Powered by Innovators Soft',
453
+ sections: [
454
+ {
455
+ title: 'Food',
456
+ rows: [
457
+ { rowId: 'nasi-goreng', title: 'Fried Rice', description: '$2.50' },
458
+ { rowId: 'mie-goreng', title: 'Fried Noodles', description: '$2.00' }
459
+ ]
460
+ }
461
+ ]
462
+ });
463
+ ```
464
+
465
+ ### 8. Typing & Presence Control
466
+
467
+ Use `createPresenceController` for manual or standalone typing/recording presence control — without needing the auto-reply system.
407
468
 
408
469
  ```javascript
409
- ### 8. Message Store (History)
470
+ const typing = client.createPresenceController();
471
+
472
+ // Show "typing..." for 2 s, then send the message — all in one call
473
+ const sent = await typing.simulateTyping(jid, 2000, async () => {
474
+ await client.sendMessage(jid, 'Here is your answer! ✅');
475
+ });
476
+
477
+ // Manual start (auto-pauses after 5 s by default)
478
+ await typing.startTyping(jid, { duration: 5000 });
479
+
480
+ // Manual stop
481
+ await typing.stopTyping(jid);
482
+
483
+ // Voice note recording indicator
484
+ await typing.startRecording(jid, { duration: 3000 });
485
+
486
+ // Stop all active indicators (e.g. on socket close)
487
+ await typing.stopAll();
488
+ ```
489
+
490
+ ### 9. Message History (Store)
410
491
 
411
492
  The library includes a robust message store to keep track of chat history, even across reloads.
412
493
 
@@ -546,6 +627,8 @@ The library includes example bot commands that you can use:
546
627
  - `!readreceiptprivacy <value>` - Update read receipts privacy (all/none)
547
628
  - `!groupaddprivacy <value>` - Update who can add you to groups (all/contacts/contact_blacklist)
548
629
  - `!disappearing <seconds>` - Update default disappearing mode (0/86400/604800/7776000)
630
+ - `!updatestatus <text>` - Update profile status
631
+ - `!updatename <text>` - Update profile name
549
632
 
550
633
  ### Interactive Messages
551
634
  - `!buttons` - Show interactive buttons
package/example.js CHANGED
@@ -9,6 +9,10 @@ const rl = readline.createInterface({
9
9
  output: process.stdout
10
10
  });
11
11
 
12
+ String.prototype.toTitleCase = function () {
13
+ return this.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
14
+ };
15
+
12
16
  const question = (text) => new Promise((resolve) => rl.question(text, resolve));
13
17
 
14
18
  async function start() {
@@ -367,6 +371,59 @@ async function start() {
367
371
  });
368
372
  break
369
373
 
374
+ case '!quickreplyv2':
375
+ await client.sendQuickReplyV2(msg.from, 'Please select an option below:', [
376
+ { id: 'btn-1', displayText: '✅ Accept' },
377
+ { id: 'btn-2', displayText: '❌ Reject' },
378
+ { id: 'btn-3', displayText: '📞 Contact Support' }
379
+ ], { footer: 'Powered by Innovators Soft' });
380
+ break
381
+
382
+ case '!urlbuttonv2':
383
+ await client.sendUrlButtonV2(msg.from, 'Visit our website for more info', [
384
+ { displayText: '🌐 Open Website', url: 'https://example.com' }
385
+ ], { title: 'Product Info', footer: 'Click to open' });
386
+ break
387
+
388
+ case '!copycodev2':
389
+ await client.sendCopyCodeV2(msg.from, 'Your OTP Code is:', '123456', '📋 Copy Code');
390
+ break
391
+
392
+ case '!combinedv2':
393
+ await client.sendCombinedButtonsV2(msg.from, 'Choose an action:', [
394
+ { type: 'reply', displayText: '🛒 Order Now', id: 'order' },
395
+ { type: 'url', displayText: '🌐 Website', url: 'https://example.com' },
396
+ { type: 'call', displayText: '📞 Phone', phoneNumber: '+923224559543' },
397
+ { type: 'copy', displayText: '📋 Copy Promo', copyCode: 'PROMO2024' }
398
+ ], { title: 'Main Menu', footer: 'Innovators Soft' });
399
+ break
400
+
401
+ case '!listv2':
402
+ await client.sendListV2(msg.from, {
403
+ title: '📋 Product Menu',
404
+ buttonText: 'View Menu',
405
+ description: 'Please select a product',
406
+ footer: 'Powered by Innovators Soft',
407
+ sections: [
408
+ {
409
+ title: 'Food',
410
+ rows: [
411
+ { rowId: 'nasi-goreng', title: 'Fried Rice', description: '$2.50' },
412
+ { rowId: 'mie-goreng', title: 'Fried Noodles', description: '$2.00' }
413
+ ]
414
+ },
415
+ {
416
+ title: 'Beverages',
417
+ rows: [
418
+ { rowId: 'es-teh', title: 'Ice Tea', description: '$0.50' },
419
+ { rowId: 'kopi', title: 'Coffee', description: '$1.00' }
420
+ ]
421
+ }
422
+ ]
423
+ });
424
+ break
425
+
426
+
370
427
  case '!call':
371
428
  try {
372
429
  if (autoCancelCallTimer) {
@@ -376,7 +433,7 @@ async function start() {
376
433
  const result = await client.initiateCall(msg.from);
377
434
  lastOutgoingCallId = result?.callId || null;
378
435
  lastOutgoingCallJid = msg.from;
379
-
436
+
380
437
  await client.sendMessage(msg.from, `Calling... CallId: ${lastOutgoingCallId || 'unknown'}`);
381
438
 
382
439
  if (lastOutgoingCallId) {
@@ -501,11 +558,18 @@ async function start() {
501
558
  `• !statusprivacy <value> - Update status privacy\n` +
502
559
  `• !readreceiptprivacy <value> - Update read receipts\n` +
503
560
  `• !groupaddprivacy <value> - Who can add to groups\n` +
504
- `• !disappearing <seconds> - Default disappearing mode\n\n` +
561
+ `• !disappearing <seconds> - Default disappearing mode\n` +
562
+ `• !updatestatus <text> - Update profile status\n` +
563
+ `• !updatename <text> - Update profile name\n\n` +
505
564
 
506
565
  `*🎛️ Templates & Buttons*\n` +
507
566
  `• !buttons - Button template\n` +
508
- `• !list - Scrollable list\n\n` +
567
+ `• !list - Scrollable list\n` +
568
+ `• !quickreplyv2 - Quick reply buttons V2\n` +
569
+ `• !urlbuttonv2 - URL button V2\n` +
570
+ `• !copycodev2 - Copy code button V2\n` +
571
+ `• !combinedv2 - Mixed buttons V2\n` +
572
+ `• !listv2 - Interactive list V2\n\n` +
509
573
 
510
574
  `*📞 Calls*\n` +
511
575
  `• !call - Initiate a voice call\n` +
@@ -532,6 +596,10 @@ async function start() {
532
596
  `• !typing - Show typing indicator\n` +
533
597
  `• !recording - Show recording indicator\n` +
534
598
  `• !paused - Clear typing or recording indicator\n` +
599
+ `• !typing_simulate - Simulate typing for 5s then send msg\n` +
600
+ `• !typing_start - Start typing with auto-pause\n` +
601
+ `• !typing_stop - Stop typing indicator\n` +
602
+ `• !recording_start - Start recording indicator\n` +
535
603
  `• !logout - End session\n\n` +
536
604
 
537
605
  `*📝 Note*:\nReplace <number> with phone number\n(without + or spaces)`
@@ -725,6 +793,40 @@ async function start() {
725
793
  await client.sendMessage(msg.from, 'Stopped typing/recording indicator sent!');
726
794
  break;
727
795
 
796
+ case '!typing_simulate':
797
+ // Show "typing..." for 5 seconds, then send the message — all in one call
798
+ await client.sendMessage(msg.from, 'Simulating typing for 5 seconds...');
799
+ const typing = client.createPresenceController();
800
+ await typing.simulateTyping(msg.from, 5000, async () => {
801
+ await client.sendMessage(msg.from, 'This message was sent after 5 seconds of typing! ✅');
802
+ });
803
+ break;
804
+
805
+ case '!typing_start':
806
+ // Manual start (auto-pauses after 5 s by default if not specified)
807
+ const typingStart = client.createPresenceController();
808
+ await typingStart.startTyping(msg.from, { duration: 10000 }); // Show for 10s
809
+ await client.sendMessage(msg.from, 'Typing indicator started for 10 seconds.');
810
+ break;
811
+
812
+ case '!typing_stop':
813
+ const typingStop = client.createPresenceController();
814
+ await typingStop.stopTyping(msg.from);
815
+ await client.sendMessage(msg.from, 'Typing indicator stopped.');
816
+ break;
817
+
818
+ case '!recording_start':
819
+ const recordingIndicator = client.createPresenceController();
820
+ await recordingIndicator.startRecording(msg.from, { duration: 5000 });
821
+ await client.sendMessage(msg.from, 'Recording indicator started for 5 seconds.');
822
+ break;
823
+
824
+ case '!typing_stop_all':
825
+ const typingStopAll = client.createPresenceController();
826
+ await typingStopAll.stopAll();
827
+ await client.sendMessage(msg.from, 'All active indicators for this controller stopped.');
828
+ break;
829
+
728
830
  case '!read':
729
831
  await client.readMessage(msg.raw.key);
730
832
  await client.sendMessage(msg.from, 'Message marked as read!');
@@ -1351,6 +1453,35 @@ async function start() {
1351
1453
  }
1352
1454
  break;
1353
1455
 
1456
+ case '!updatestatus':
1457
+ try {
1458
+ if (!args) {
1459
+ await client.sendMessage(msg.from, '❌ Please provide a new status.\nUsage: !updatestatus <text>');
1460
+ break;
1461
+ }
1462
+ await client.updateProfileStatus(args.trim());
1463
+ await client.sendMessage(msg.from, `✅ Profile status updated successfully!`);
1464
+ } catch (error) {
1465
+ console.error('Error updating profile status:', error);
1466
+ await client.sendMessage(msg.from, `❌ Failed to update profile status: ${error.message}`);
1467
+ }
1468
+ break;
1469
+
1470
+ case '!updatename':
1471
+ try {
1472
+ if (!args) {
1473
+ await client.sendMessage(msg.from, '❌ Please provide a new name.\nUsage: !updatename <text>');
1474
+ break;
1475
+ }
1476
+ const newName = args.trim().toTitleCase();
1477
+ await client.updateProfileName(newName);
1478
+ await client.sendMessage(msg.from, `✅ *${newName}* \nProfile name updated successfully!`);
1479
+ } catch (error) {
1480
+ console.error('Error updating profile name:', error);
1481
+ await client.sendMessage(msg.from, `❌ Failed to update profile name: ${error.message}`);
1482
+ }
1483
+ break;
1484
+
1354
1485
  case '!messages':
1355
1486
  const history = client.getStoredMessages(msg.from);
1356
1487
  let historyText = `*💾 Stored Messages for this chat (${history.length}):*\n\n`;
package/index.js CHANGED
@@ -14,7 +14,14 @@ const {
14
14
  // Anti-Delete
15
15
  MessageStore,
16
16
  createMessageStoreHandler,
17
- createAntiDeleteHandler
17
+ createAntiDeleteHandler,
18
+ createTypingIndicator,
19
+ generateInteractiveButtonMessage,
20
+ generateInteractiveListMessage,
21
+ generateCombinedButtons,
22
+ generateCopyCodeButton,
23
+ generateUrlButtonMessage,
24
+ generateQuickReplyButtons
18
25
  } = require('@innovatorssoft/baileys');
19
26
 
20
27
  const { Sticker, StickerTypes } = require('wa-sticker-formatter');
@@ -119,16 +126,15 @@ class WhatsAppClient extends EventEmitter {
119
126
 
120
127
  async connect() {
121
128
  try {
129
+ if (this._connectionState === 'connecting' && this.sock) {
130
+ return; // Prevent concurrent connection attempts
131
+ }
122
132
 
123
133
  if (this._connectionState !== 'connecting') {
124
134
  this._connectionState = 'connecting';
125
135
  this.emit('connecting', 'Connecting to WhatsApp...');
126
136
  }
127
137
 
128
- const browserConfig = this.authmethod === 'pairing'
129
- ? Browsers.iOS('chrome')
130
- : ["Innovators Soft", "chrome", "1000.26100.275.0"];
131
-
132
138
  const { version: baileysVersion, isLatest: baileysIsLatest } = await fetchLatestBaileysVersion();
133
139
  const { version: waWebVersion, isLatest: waWebIsLatest } = await fetchLatestWaWebVersion();
134
140
 
@@ -147,7 +153,7 @@ class WhatsAppClient extends EventEmitter {
147
153
  generateHighQualityLinkPreview: true,
148
154
  linkPreviewImageThumbnailWidth: 192,
149
155
  emitOwnEvents: true,
150
- browser: browserConfig,
156
+ browser: Browsers.android('Innovators Soft'),
151
157
  version: waWebVersion,
152
158
  cachedGroupMetadata: async (jid) => {
153
159
  const cached = this.groupMetadataCache.get(jid);
@@ -440,6 +446,7 @@ class WhatsAppClient extends EventEmitter {
440
446
 
441
447
  try {
442
448
  for (const reaction of reactions) {
449
+
443
450
  // Get the chat JID, preferring PN over LID
444
451
  let jid = reaction.key.remoteJid;
445
452
  if (typeof reaction.key.remoteJidAlt === 'string' &&
@@ -913,6 +920,80 @@ class WhatsAppClient extends EventEmitter {
913
920
  throw error;
914
921
  }
915
922
  }
923
+
924
+ /**
925
+ * Send Quick Reply Buttons (V2)
926
+ * @param {string} jid - Target JID
927
+ * @param {string} text - Message text
928
+ * @param {Array<object>} buttons - Array of { id, displayText }
929
+ * @param {object} options - { footer }
930
+ */
931
+ async sendQuickReplyV2(jid, text, buttons, options = {}) {
932
+ if (!this.isConnected) throw new Error('Client is not connected');
933
+ const message = generateQuickReplyButtons(text, buttons, options);
934
+ return await this.sock.sendMessage(jid, message, { ai: true });
935
+ }
936
+
937
+ /**
938
+ * Send Generic Interactive Button Message (V2)
939
+ * @param {string} jid - Target JID
940
+ * @param {object} options - Button options
941
+ */
942
+ async sendInteractiveButtonV2(jid, options) {
943
+ if (!this.isConnected) throw new Error('Client is not connected');
944
+ const message = generateInteractiveButtonMessage(options);
945
+ return await this.sock.sendMessage(jid, message, { ai: true });
946
+ }
947
+
948
+ /**
949
+ * Send URL Button (V2)
950
+ * @param {string} jid - Target JID
951
+ * @param {string} text - Message text
952
+ * @param {Array<object>} buttons - Array of { displayText, url }
953
+ * @param {object} options - { title, footer }
954
+ */
955
+ async sendUrlButtonV2(jid, text, buttons, options = {}) {
956
+ if (!this.isConnected) throw new Error('Client is not connected');
957
+ const message = generateUrlButtonMessage(text, buttons, options);
958
+ return await this.sock.sendMessage(jid, message, { ai: true });
959
+ }
960
+
961
+ /**
962
+ * Send Copy Code Button (V2)
963
+ * @param {string} jid - Target JID
964
+ * @param {string} text - Message text
965
+ * @param {string} code - Code to be copied
966
+ * @param {string} buttonText - Text on the copy button
967
+ */
968
+ async sendCopyCodeV2(jid, text, code, buttonText) {
969
+ if (!this.isConnected) throw new Error('Client is not connected');
970
+ const message = generateCopyCodeButton(text, code, buttonText);
971
+ return await this.sock.sendMessage(jid, message, { ai: true });
972
+ }
973
+
974
+ /**
975
+ * Send Combined Buttons (V2)
976
+ * @param {string} jid - Target JID
977
+ * @param {string} text - Message text
978
+ * @param {Array<object>} buttons - Mix of { type: 'reply'|'url'|'call'|'copy', ... }
979
+ * @param {object} options - { title, footer }
980
+ */
981
+ async sendCombinedButtonsV2(jid, text, buttons, options = {}) {
982
+ if (!this.isConnected) throw new Error('Client is not connected');
983
+ const message = generateCombinedButtons(text, buttons, options);
984
+ return await this.sock.sendMessage(jid, message, { ai: true });
985
+ }
986
+
987
+ /**
988
+ * Send Interactive List Message (V2)
989
+ * @param {string} jid - Target JID
990
+ * @param {object} options - List options (title, buttonText, description, footer, sections)
991
+ */
992
+ async sendListV2(jid, options) {
993
+ if (!this.isConnected) throw new Error('Client is not connected');
994
+ const message = generateInteractiveListMessage(options);
995
+ return await this.sock.sendMessage(jid, message, { ai: true });
996
+ }
916
997
  /**
917
998
  * Send an external ad reply with a local image
918
999
  * @param {string} number - The phone number to send the ad to
@@ -1294,6 +1375,17 @@ class WhatsAppClient extends EventEmitter {
1294
1375
  await this.sock.sendPresenceUpdate("paused", jid);
1295
1376
  }
1296
1377
 
1378
+ /**
1379
+ * Create a typing indicator controller for manual or standalone presence control
1380
+ * @returns {object} The typing indicator controller
1381
+ */
1382
+ createPresenceController() {
1383
+ if (!this.sock) throw new Error('Client is not connected');
1384
+ return createTypingIndicator(
1385
+ (jid, presence) => this.sock.sendPresenceUpdate(presence, jid)
1386
+ );
1387
+ }
1388
+
1297
1389
  /**
1298
1390
  * Reinitialize the WhatsApp client by clearing the session and reconnecting
1299
1391
  * @returns {Promise<void>}
@@ -2374,6 +2466,42 @@ class WhatsAppClient extends EventEmitter {
2374
2466
  }
2375
2467
  }
2376
2468
 
2469
+ /**
2470
+ * Update profile status (about)
2471
+ * @param {string} status - The new status message
2472
+ * @returns {Promise<void>}
2473
+ * @throws {Error} If client is not connected or update fails
2474
+ */
2475
+ async updateProfileStatus(status) {
2476
+ if (!this.isConnected) {
2477
+ throw new Error('Client is not connected');
2478
+ }
2479
+ try {
2480
+ await this.sock.updateProfileStatus(status);
2481
+ } catch (error) {
2482
+ console.error('Error updating profile status:', error);
2483
+ throw error;
2484
+ }
2485
+ }
2486
+
2487
+ /**
2488
+ * Update profile name
2489
+ * @param {string} name - The new profile name
2490
+ * @returns {Promise<void>}
2491
+ * @throws {Error} If client is not connected or update fails
2492
+ */
2493
+ async updateProfileName(name) {
2494
+ if (!this.isConnected) {
2495
+ throw new Error('Client is not connected');
2496
+ }
2497
+ try {
2498
+ await this.sock.updateProfileName(name);
2499
+ } catch (error) {
2500
+ console.error('Error updating profile name:', error);
2501
+ throw error;
2502
+ }
2503
+ }
2504
+
2377
2505
  /**
2378
2506
  * Add EXIF metadata to an existing WebP buffer
2379
2507
  * @param {Buffer} buffer - WebP buffer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innovatorssoft/innovators-bot2",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "WhatsApp API",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/publish-dual.js DELETED
@@ -1,40 +0,0 @@
1
- const { execSync } = require('child_process');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- const packagePath = path.join(__dirname, 'package.json');
6
-
7
- // Read the original package.json
8
- const originalPackage = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
9
- const originalName = originalPackage.name;
10
-
11
- console.log('🚀 Starting dual npm publish...\n');
12
-
13
- try {
14
- // Step 1: Publish as unscoped package (innovators-bot2)
15
- console.log(`📦 Publishing as "${originalName}"...`);
16
- execSync('npm publish', { stdio: 'inherit', cwd: __dirname });
17
- console.log(`✅ Successfully published "${originalName}"\n`);
18
-
19
- // Step 2: Change name to scoped package
20
- const scopedName = '@innovatorssoft/innovators-bot2';
21
- console.log(`📦 Publishing as "${scopedName}"...`);
22
-
23
- originalPackage.name = scopedName;
24
- fs.writeFileSync(packagePath, JSON.stringify(originalPackage, null, 2) + '\n');
25
-
26
- // Step 3: Publish as scoped package with public access
27
- execSync('npm publish --access public', { stdio: 'inherit', cwd: __dirname });
28
- console.log(`✅ Successfully published "${scopedName}"\n`);
29
-
30
- } catch (error) {
31
- console.error('❌ Error during publish:', error.message);
32
- process.exitCode = 1;
33
- } finally {
34
- // Step 4: Always restore original package.json
35
- console.log('🔄 Restoring original package.json...');
36
- originalPackage.name = originalName;
37
- fs.writeFileSync(packagePath, JSON.stringify(originalPackage, null, 2) + '\n');
38
- console.log('✅ Original package.json restored');
39
- console.log('\n🎉 Dual publish complete!');
40
- }