From d7f7889b0f6c38753b2152d4026a52fde30e405a Mon Sep 17 00:00:00 2001 From: Tomas Melone <60193559+tomimelo@users.noreply.github.com> Date: Sat, 19 Feb 2022 20:18:34 +0100 Subject: [PATCH 01/50] fix: Remove duplicate identifier (#1205) --- index.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 0f14faf..7039752 100644 --- a/index.d.ts +++ b/index.d.ts @@ -446,7 +446,6 @@ declare namespace WAWebJS { LIST = 'list', LIST_RESPONSE = 'list_response', BUTTONS_RESPONSE = 'buttons_response', - PAYMENT = 'payment', BROADCAST_NOTIFICATION = 'broadcast_notification', CALL_LOG = 'call_log', CIPHERTEXT = 'ciphertext', From 66283da90392141a30a2909079635d337c5f4584 Mon Sep 17 00:00:00 2001 From: Shir Serlui <70711723+shirser121@users.noreply.github.com> Date: Sat, 19 Feb 2022 21:31:23 +0200 Subject: [PATCH 02/50] Fix message.delete(true) (#1211) * Fix message.delete(true) * Fix for tests --- src/structures/Message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index d1e0e87..5f6e4f0 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -389,7 +389,7 @@ class Message extends Base { let msg = window.Store.Msg.get(msgId); if (everyone && msg.id.fromMe && msg._canRevoke()) { - return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], true); + return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], {type: 'Sender'}); } return window.Store.Cmd.sendDeleteMsgs(msg.chat, [msg], true); From e29354fca34cc3cd4a8e7242481eed402fe73b86 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:45:21 -0400 Subject: [PATCH 03/50] Update supported WhatsApp Web version to v2.2204.13 (#1210) Co-authored-by: pedroslopez --- README.md | 2 +- tools/version-checker/.version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f76c99..4cd80ab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![npm](https://img.shields.io/npm/v/whatsapp-web.js.svg)](https://www.npmjs.com/package/whatsapp-web.js) [![Depfu](https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg)](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ![WhatsApp_Web 2.2202.12](https://img.shields.io/badge/WhatsApp_Web-2.2202.12-brightgreen.svg) [![Discord Chat](https://img.shields.io/discord/698610475432411196.svg?logo=discord)](https://discord.gg/H7DqQs4) +[![npm](https://img.shields.io/npm/v/whatsapp-web.js.svg)](https://www.npmjs.com/package/whatsapp-web.js) [![Depfu](https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg)](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ![WhatsApp_Web 2.2204.13](https://img.shields.io/badge/WhatsApp_Web-2.2204.13-brightgreen.svg) [![Discord Chat](https://img.shields.io/discord/698610475432411196.svg?logo=discord)](https://discord.gg/H7DqQs4) # whatsapp-web.js A WhatsApp API client that connects through the WhatsApp Web browser app diff --git a/tools/version-checker/.version b/tools/version-checker/.version index bfdd224..0e1e309 100644 --- a/tools/version-checker/.version +++ b/tools/version-checker/.version @@ -1 +1 @@ -2.2202.12 \ No newline at end of file +2.2204.13 \ No newline at end of file From 5d83b45ed4a623ab4c9f1f3c6d6ee5bad178da95 Mon Sep 17 00:00:00 2001 From: Pedro Lopez Date: Sat, 19 Feb 2022 15:48:03 -0400 Subject: [PATCH 04/50] chore: mark version v1.15.7 --- docs/Base.html | 6 +++--- docs/BusinessContact.html | 6 +++--- docs/Buttons.html | 6 +++--- docs/Call.html | 6 +++--- docs/Chat.html | 6 +++--- docs/Client.html | 6 +++--- docs/Client.js.html | 6 +++--- docs/ClientInfo.html | 6 +++--- docs/Contact.html | 6 +++--- docs/GroupChat.html | 6 +++--- docs/GroupNotification.html | 6 +++--- docs/InterfaceController.html | 6 +++--- docs/Label.html | 6 +++--- docs/List.html | 6 +++--- docs/Location.html | 6 +++--- docs/Message.html | 6 +++--- docs/MessageMedia.html | 6 +++--- docs/Order.html | 6 +++--- docs/PrivateChat.html | 6 +++--- docs/PrivateContact.html | 6 +++--- docs/Product.html | 6 +++--- docs/Util.html | 6 +++--- docs/global.html | 6 +++--- docs/index.html | 8 ++++---- docs/structures_Base.js.html | 6 +++--- docs/structures_BusinessContact.js.html | 6 +++--- docs/structures_Buttons.js.html | 6 +++--- docs/structures_Call.js.html | 6 +++--- docs/structures_Chat.js.html | 6 +++--- docs/structures_ClientInfo.js.html | 6 +++--- docs/structures_Contact.js.html | 6 +++--- docs/structures_GroupChat.js.html | 6 +++--- docs/structures_GroupNotification.js.html | 6 +++--- docs/structures_Label.js.html | 6 +++--- docs/structures_List.js.html | 6 +++--- docs/structures_Location.js.html | 6 +++--- docs/structures_Message.js.html | 8 ++++---- docs/structures_MessageMedia.js.html | 6 +++--- docs/structures_Order.js.html | 6 +++--- docs/structures_Payment.js.html | 6 +++--- docs/structures_PrivateChat.js.html | 6 +++--- docs/structures_PrivateContact.js.html | 6 +++--- docs/structures_Product.js.html | 6 +++--- docs/structures_ProductMetadata.js.html | 6 +++--- docs/util_Constants.js.html | 6 +++--- docs/util_InterfaceController.js.html | 6 +++--- docs/util_Util.js.html | 6 +++--- package.json | 2 +- 48 files changed, 144 insertions(+), 144 deletions(-) diff --git a/docs/Base.html b/docs/Base.html index 431e533..d7a1f13 100644 --- a/docs/Base.html +++ b/docs/Base.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Base + whatsapp-web.js 1.15.7 » Class: Base @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@
diff --git a/docs/BusinessContact.html b/docs/BusinessContact.html index 32f1ef2..d399c19 100644 --- a/docs/BusinessContact.html +++ b/docs/BusinessContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: BusinessContact + whatsapp-web.js 1.15.7 » Class: BusinessContact @@ -15,7 +15,7 @@ @@ -314,7 +314,7 @@
diff --git a/docs/Buttons.html b/docs/Buttons.html index a6e6b29..3974cb0 100644 --- a/docs/Buttons.html +++ b/docs/Buttons.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Buttons + whatsapp-web.js 1.15.7 » Class: Buttons @@ -15,7 +15,7 @@ @@ -234,7 +234,7 @@ Returns: [{ buttonId:'customId',buttonText:{'displayText':&#
diff --git a/docs/Call.html b/docs/Call.html index 5e027ba..09556f6 100644 --- a/docs/Call.html +++ b/docs/Call.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Call + whatsapp-web.js 1.15.7 » Class: Call @@ -15,7 +15,7 @@ @@ -144,7 +144,7 @@
diff --git a/docs/Chat.html b/docs/Chat.html index ba808ca..13f4f2a 100644 --- a/docs/Chat.html +++ b/docs/Chat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Chat + whatsapp-web.js 1.15.7 » Class: Chat @@ -15,7 +15,7 @@ @@ -483,7 +483,7 @@
diff --git a/docs/Client.html b/docs/Client.html index e05aad7..d2d1b23 100644 --- a/docs/Client.html +++ b/docs/Client.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Client + whatsapp-web.js 1.15.7 » Class: Client @@ -15,7 +15,7 @@ @@ -2416,7 +2416,7 @@
diff --git a/docs/Client.js.html b/docs/Client.js.html index 5616fe9..0cfcbaf 100644 --- a/docs/Client.js.html +++ b/docs/Client.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: Client.js + whatsapp-web.js 1.15.7 » Source: Client.js @@ -15,7 +15,7 @@ @@ -1069,7 +1069,7 @@ module.exports = Client;
diff --git a/docs/ClientInfo.html b/docs/ClientInfo.html index 1ea8a5e..11c25bc 100644 --- a/docs/ClientInfo.html +++ b/docs/ClientInfo.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: ClientInfo + whatsapp-web.js 1.15.7 » Class: ClientInfo @@ -15,7 +15,7 @@ @@ -238,7 +238,7 @@
diff --git a/docs/Contact.html b/docs/Contact.html index e5d77bc..e13b9ab 100644 --- a/docs/Contact.html +++ b/docs/Contact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Contact + whatsapp-web.js 1.15.7 » Class: Contact @@ -15,7 +15,7 @@ @@ -281,7 +281,7 @@
diff --git a/docs/GroupChat.html b/docs/GroupChat.html index b697aca..013d8cd 100644 --- a/docs/GroupChat.html +++ b/docs/GroupChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: GroupChat + whatsapp-web.js 1.15.7 » Class: GroupChat @@ -15,7 +15,7 @@ @@ -921,7 +921,7 @@
diff --git a/docs/GroupNotification.html b/docs/GroupNotification.html index f446b0b..66c98e7 100644 --- a/docs/GroupNotification.html +++ b/docs/GroupNotification.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: GroupNotification + whatsapp-web.js 1.15.7 » Class: GroupNotification @@ -15,7 +15,7 @@ @@ -233,7 +233,7 @@
diff --git a/docs/InterfaceController.html b/docs/InterfaceController.html index d351316..ed99c12 100644 --- a/docs/InterfaceController.html +++ b/docs/InterfaceController.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: InterfaceController + whatsapp-web.js 1.15.7 » Class: InterfaceController @@ -15,7 +15,7 @@ @@ -382,7 +382,7 @@
diff --git a/docs/Label.html b/docs/Label.html index fa8a910..10c052a 100644 --- a/docs/Label.html +++ b/docs/Label.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Label + whatsapp-web.js 1.15.7 » Class: Label @@ -15,7 +15,7 @@ @@ -163,7 +163,7 @@
diff --git a/docs/List.html b/docs/List.html index 1d8156c..8c5a7ea 100644 --- a/docs/List.html +++ b/docs/List.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: List + whatsapp-web.js 1.15.7 » Class: List @@ -15,7 +15,7 @@ @@ -256,7 +256,7 @@ Returns: [{'title':'sectionTitle','rows':[{'r
diff --git a/docs/Location.html b/docs/Location.html index 6a672e8..e62ba9b 100644 --- a/docs/Location.html +++ b/docs/Location.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Location + whatsapp-web.js 1.15.7 » Class: Location @@ -15,7 +15,7 @@ @@ -149,7 +149,7 @@
diff --git a/docs/Message.html b/docs/Message.html index d9b2f59..7c9d916 100644 --- a/docs/Message.html +++ b/docs/Message.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Message + whatsapp-web.js 1.15.7 » Class: Message @@ -15,7 +15,7 @@ @@ -580,7 +580,7 @@
diff --git a/docs/MessageMedia.html b/docs/MessageMedia.html index 0442fd7..df6284b 100644 --- a/docs/MessageMedia.html +++ b/docs/MessageMedia.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: MessageMedia + whatsapp-web.js 1.15.7 » Class: MessageMedia @@ -15,7 +15,7 @@ @@ -343,7 +343,7 @@
diff --git a/docs/Order.html b/docs/Order.html index 39a3668..471e31f 100644 --- a/docs/Order.html +++ b/docs/Order.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Order + whatsapp-web.js 1.15.7 » Class: Order @@ -15,7 +15,7 @@ @@ -102,7 +102,7 @@
diff --git a/docs/PrivateChat.html b/docs/PrivateChat.html index 0990185..51c8549 100644 --- a/docs/PrivateChat.html +++ b/docs/PrivateChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: PrivateChat + whatsapp-web.js 1.15.7 » Class: PrivateChat @@ -15,7 +15,7 @@ @@ -519,7 +519,7 @@
diff --git a/docs/PrivateContact.html b/docs/PrivateContact.html index 388084a..14db873 100644 --- a/docs/PrivateContact.html +++ b/docs/PrivateContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: PrivateContact + whatsapp-web.js 1.15.7 » Class: PrivateContact @@ -15,7 +15,7 @@ @@ -307,7 +307,7 @@
diff --git a/docs/Product.html b/docs/Product.html index 6359b7a..083153e 100644 --- a/docs/Product.html +++ b/docs/Product.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Product + whatsapp-web.js 1.15.7 » Class: Product @@ -15,7 +15,7 @@ @@ -127,7 +127,7 @@
diff --git a/docs/Util.html b/docs/Util.html index 696d76d..e7ff1f4 100644 --- a/docs/Util.html +++ b/docs/Util.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Class: Util + whatsapp-web.js 1.15.7 » Class: Util @@ -15,7 +15,7 @@ @@ -243,7 +243,7 @@
diff --git a/docs/global.html b/docs/global.html index b373bf7..6b9ae8d 100644 --- a/docs/global.html +++ b/docs/global.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Globals + whatsapp-web.js 1.15.7 » Globals @@ -15,7 +15,7 @@ @@ -1980,7 +1980,7 @@
diff --git a/docs/index.html b/docs/index.html index aa8315c..64fc62f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Home + whatsapp-web.js 1.15.7 » Home @@ -15,7 +15,7 @@ @@ -27,7 +27,7 @@
@@ -3031,7 +3031,7 @@ client.initialize();
diff --git a/docs/structures_Base.js.html b/docs/structures_Base.js.html index d2a8249..27d1de8 100644 --- a/docs/structures_Base.js.html +++ b/docs/structures_Base.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Base.js + whatsapp-web.js 1.15.7 » Source: structures/Base.js @@ -15,7 +15,7 @@ @@ -60,7 +60,7 @@ module.exports = Base;
diff --git a/docs/structures_BusinessContact.js.html b/docs/structures_BusinessContact.js.html index 9f69b7e..7a042a2 100644 --- a/docs/structures_BusinessContact.js.html +++ b/docs/structures_BusinessContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/BusinessContact.js + whatsapp-web.js 1.15.7 » Source: structures/BusinessContact.js @@ -15,7 +15,7 @@ @@ -59,7 +59,7 @@ module.exports = BusinessContact;
diff --git a/docs/structures_Buttons.js.html b/docs/structures_Buttons.js.html index 1fb8ace..d7ac74e 100644 --- a/docs/structures_Buttons.js.html +++ b/docs/structures_Buttons.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Buttons.js + whatsapp-web.js 1.15.7 » Source: structures/Buttons.js @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@ module.exports = Buttons;
diff --git a/docs/structures_Call.js.html b/docs/structures_Call.js.html index 77a86e1..ddef6ac 100644 --- a/docs/structures_Call.js.html +++ b/docs/structures_Call.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Call.js + whatsapp-web.js 1.15.7 » Source: structures/Call.js @@ -15,7 +15,7 @@ @@ -106,7 +106,7 @@ module.exports = Call;
diff --git a/docs/structures_Chat.js.html b/docs/structures_Chat.js.html index 917d234..08d131b 100644 --- a/docs/structures_Chat.js.html +++ b/docs/structures_Chat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Chat.js + whatsapp-web.js 1.15.7 » Source: structures/Chat.js @@ -15,7 +15,7 @@ @@ -290,7 +290,7 @@ module.exports = Chat;
diff --git a/docs/structures_ClientInfo.js.html b/docs/structures_ClientInfo.js.html index 1bea899..61cc3c3 100644 --- a/docs/structures_ClientInfo.js.html +++ b/docs/structures_ClientInfo.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/ClientInfo.js + whatsapp-web.js 1.15.7 » Source: structures/ClientInfo.js @@ -15,7 +15,7 @@ @@ -108,7 +108,7 @@ module.exports = ClientInfo;
diff --git a/docs/structures_Contact.js.html b/docs/structures_Contact.js.html index 951e985..c16dd8d 100644 --- a/docs/structures_Contact.js.html +++ b/docs/structures_Contact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Contact.js + whatsapp-web.js 1.15.7 » Source: structures/Contact.js @@ -15,7 +15,7 @@ @@ -236,7 +236,7 @@ module.exports = Contact;
diff --git a/docs/structures_GroupChat.js.html b/docs/structures_GroupChat.js.html index cc2daf6..fd3eadc 100644 --- a/docs/structures_GroupChat.js.html +++ b/docs/structures_GroupChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/GroupChat.js + whatsapp-web.js 1.15.7 » Source: structures/GroupChat.js @@ -15,7 +15,7 @@ @@ -239,7 +239,7 @@ module.exports = GroupChat;
diff --git a/docs/structures_GroupNotification.js.html b/docs/structures_GroupNotification.js.html index c36ffb4..334bd35 100644 --- a/docs/structures_GroupNotification.js.html +++ b/docs/structures_GroupNotification.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/GroupNotification.js + whatsapp-web.js 1.15.7 » Source: structures/GroupNotification.js @@ -15,7 +15,7 @@ @@ -143,7 +143,7 @@ module.exports = GroupNotification;
diff --git a/docs/structures_Label.js.html b/docs/structures_Label.js.html index d0ddbda..d9d0255 100644 --- a/docs/structures_Label.js.html +++ b/docs/structures_Label.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Label.js + whatsapp-web.js 1.15.7 » Source: structures/Label.js @@ -15,7 +15,7 @@ @@ -88,7 +88,7 @@ module.exports = Label;
diff --git a/docs/structures_List.js.html b/docs/structures_List.js.html index 600a35a..22b9506 100644 --- a/docs/structures_List.js.html +++ b/docs/structures_List.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/List.js + whatsapp-web.js 1.15.7 » Source: structures/List.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@ module.exports = List;
diff --git a/docs/structures_Location.js.html b/docs/structures_Location.js.html index 351ab27..a8df945 100644 --- a/docs/structures_Location.js.html +++ b/docs/structures_Location.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Location.js + whatsapp-web.js 1.15.7 » Source: structures/Location.js @@ -15,7 +15,7 @@ @@ -71,7 +71,7 @@ module.exports = Location;
diff --git a/docs/structures_Message.js.html b/docs/structures_Message.js.html index 38ec6bd..0b980aa 100644 --- a/docs/structures_Message.js.html +++ b/docs/structures_Message.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Message.js + whatsapp-web.js 1.15.7 » Source: structures/Message.js @@ -15,7 +15,7 @@ @@ -420,7 +420,7 @@ class Message extends Base { let msg = window.Store.Msg.get(msgId); if (everyone &amp;&amp; msg.id.fromMe &amp;&amp; msg._canRevoke()) { - return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], true); + return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], {type: 'Sender'}); } return window.Store.Cmd.sendDeleteMsgs(msg.chat, [msg], true); @@ -525,7 +525,7 @@ module.exports = Message;
diff --git a/docs/structures_MessageMedia.js.html b/docs/structures_MessageMedia.js.html index da84bcd..a77e3b4 100644 --- a/docs/structures_MessageMedia.js.html +++ b/docs/structures_MessageMedia.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/MessageMedia.js + whatsapp-web.js 1.15.7 » Source: structures/MessageMedia.js @@ -15,7 +15,7 @@ @@ -142,7 +142,7 @@ module.exports = MessageMedia;
diff --git a/docs/structures_Order.js.html b/docs/structures_Order.js.html index fd519bd..282ee27 100644 --- a/docs/structures_Order.js.html +++ b/docs/structures_Order.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Order.js + whatsapp-web.js 1.15.7 » Source: structures/Order.js @@ -15,7 +15,7 @@ @@ -90,7 +90,7 @@ module.exports = Order;
diff --git a/docs/structures_Payment.js.html b/docs/structures_Payment.js.html index d513f0f..409a704 100644 --- a/docs/structures_Payment.js.html +++ b/docs/structures_Payment.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Payment.js + whatsapp-web.js 1.15.7 » Source: structures/Payment.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@ module.exports = Payment;
diff --git a/docs/structures_PrivateChat.js.html b/docs/structures_PrivateChat.js.html index 0db9881..a880fad 100644 --- a/docs/structures_PrivateChat.js.html +++ b/docs/structures_PrivateChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/PrivateChat.js + whatsapp-web.js 1.15.7 » Source: structures/PrivateChat.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@ module.exports = PrivateChat;
diff --git a/docs/structures_PrivateContact.js.html b/docs/structures_PrivateContact.js.html index 17ced7e..d442ce8 100644 --- a/docs/structures_PrivateContact.js.html +++ b/docs/structures_PrivateContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/PrivateContact.js + whatsapp-web.js 1.15.7 » Source: structures/PrivateContact.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@ module.exports = PrivateContact;
diff --git a/docs/structures_Product.js.html b/docs/structures_Product.js.html index ec9a754..6141be0 100644 --- a/docs/structures_Product.js.html +++ b/docs/structures_Product.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/Product.js + whatsapp-web.js 1.15.7 » Source: structures/Product.js @@ -15,7 +15,7 @@ @@ -106,7 +106,7 @@ module.exports = Product;
diff --git a/docs/structures_ProductMetadata.js.html b/docs/structures_ProductMetadata.js.html index 856fd1d..8908cd0 100644 --- a/docs/structures_ProductMetadata.js.html +++ b/docs/structures_ProductMetadata.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: structures/ProductMetadata.js + whatsapp-web.js 1.15.7 » Source: structures/ProductMetadata.js @@ -15,7 +15,7 @@ @@ -63,7 +63,7 @@ module.exports = ProductMetadata;
diff --git a/docs/util_Constants.js.html b/docs/util_Constants.js.html index 165cd8b..8c6018f 100644 --- a/docs/util_Constants.js.html +++ b/docs/util_Constants.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: util/Constants.js + whatsapp-web.js 1.15.7 » Source: util/Constants.js @@ -15,7 +15,7 @@ @@ -200,7 +200,7 @@ exports.MessageAck = {
diff --git a/docs/util_InterfaceController.js.html b/docs/util_InterfaceController.js.html index bac96c8..55442c5 100644 --- a/docs/util_InterfaceController.js.html +++ b/docs/util_InterfaceController.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: util/InterfaceController.js + whatsapp-web.js 1.15.7 » Source: util/InterfaceController.js @@ -15,7 +15,7 @@ @@ -160,7 +160,7 @@ module.exports = InterfaceController;
diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index 8d4eb75..be575cf 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.6 » Source: util/Util.js + whatsapp-web.js 1.15.7 » Source: util/Util.js @@ -15,7 +15,7 @@ @@ -242,7 +242,7 @@ module.exports = Util;
diff --git a/package.json b/package.json index 9e45e3a..96178fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "whatsapp-web.js", - "version": "1.15.6", + "version": "1.15.7", "description": "Library for interacting with the WhatsApp Web API ", "main": "./index.js", "typings": "./index.d.ts", From f0e49efcf908277900cc460df57d6f6ff13a2340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=BC=BA=20L=E1=B4=87G=CC=B8=E1=B4=87=C9=B4D=20=E0=BC=BB?= <39593002+jtourisNS@users.noreply.github.com> Date: Fri, 25 Feb 2022 18:11:28 -0300 Subject: [PATCH 05/50] [Main Branch]: Fix Cannot read properties of undefined (reading 'Socket') (#1249) Co-authored-by: Joaquin Touris --- src/util/Injected.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Injected.js b/src/util/Injected.js index f125f83..6031752 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -6,7 +6,7 @@ exports.ExposeStore = (moduleRaidStr) => { // eslint-disable-next-line no-undef window.mR = moduleRaid(); window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); - window.Store.AppState = window.mR.findModule('STREAM')[0].Socket; + window.Store.AppState = window.mR.findModule('Socket')[0].Socket; window.Store.Conn = window.mR.findModule('Conn')[0].Conn; window.Store.Wap = window.mR.findModule('queryLinkPreview')[0].default; window.Store.SendSeen = window.mR.findModule('sendSeen')[0]; From bd5cfdc936bd3842d55c50478ebbf66748beb2a1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Feb 2022 17:43:30 -0400 Subject: [PATCH 06/50] Update supported WhatsApp Web version to v2.2206.5 (#1247) Co-authored-by: pedroslopez --- README.md | 2 +- tools/version-checker/.version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4cd80ab..3058048 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![npm](https://img.shields.io/npm/v/whatsapp-web.js.svg)](https://www.npmjs.com/package/whatsapp-web.js) [![Depfu](https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg)](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ![WhatsApp_Web 2.2204.13](https://img.shields.io/badge/WhatsApp_Web-2.2204.13-brightgreen.svg) [![Discord Chat](https://img.shields.io/discord/698610475432411196.svg?logo=discord)](https://discord.gg/H7DqQs4) +[![npm](https://img.shields.io/npm/v/whatsapp-web.js.svg)](https://www.npmjs.com/package/whatsapp-web.js) [![Depfu](https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg)](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ![WhatsApp_Web 2.2206.5](https://img.shields.io/badge/WhatsApp_Web-2.2206.5-brightgreen.svg) [![Discord Chat](https://img.shields.io/discord/698610475432411196.svg?logo=discord)](https://discord.gg/H7DqQs4) # whatsapp-web.js A WhatsApp API client that connects through the WhatsApp Web browser app diff --git a/tools/version-checker/.version b/tools/version-checker/.version index 0e1e309..c7e60d6 100644 --- a/tools/version-checker/.version +++ b/tools/version-checker/.version @@ -1 +1 @@ -2.2204.13 \ No newline at end of file +2.2206.5 \ No newline at end of file From 8ef37b68aefe58c3ddea068f85f1808c7712b9f7 Mon Sep 17 00:00:00 2001 From: Pedro Lopez Date: Fri, 25 Feb 2022 17:45:33 -0400 Subject: [PATCH 07/50] chore: mark version v1.15.8 --- docs/Base.html | 6 +++--- docs/BusinessContact.html | 6 +++--- docs/Buttons.html | 6 +++--- docs/Call.html | 6 +++--- docs/Chat.html | 6 +++--- docs/Client.html | 6 +++--- docs/Client.js.html | 6 +++--- docs/ClientInfo.html | 6 +++--- docs/Contact.html | 6 +++--- docs/GroupChat.html | 6 +++--- docs/GroupNotification.html | 6 +++--- docs/InterfaceController.html | 6 +++--- docs/Label.html | 6 +++--- docs/List.html | 6 +++--- docs/Location.html | 6 +++--- docs/Message.html | 6 +++--- docs/MessageMedia.html | 6 +++--- docs/Order.html | 6 +++--- docs/PrivateChat.html | 6 +++--- docs/PrivateContact.html | 6 +++--- docs/Product.html | 6 +++--- docs/Util.html | 6 +++--- docs/global.html | 6 +++--- docs/index.html | 10 +++++----- docs/structures_Base.js.html | 6 +++--- docs/structures_BusinessContact.js.html | 6 +++--- docs/structures_Buttons.js.html | 6 +++--- docs/structures_Call.js.html | 6 +++--- docs/structures_Chat.js.html | 6 +++--- docs/structures_ClientInfo.js.html | 6 +++--- docs/structures_Contact.js.html | 6 +++--- docs/structures_GroupChat.js.html | 6 +++--- docs/structures_GroupNotification.js.html | 6 +++--- docs/structures_Label.js.html | 6 +++--- docs/structures_List.js.html | 6 +++--- docs/structures_Location.js.html | 6 +++--- docs/structures_Message.js.html | 6 +++--- docs/structures_MessageMedia.js.html | 6 +++--- docs/structures_Order.js.html | 6 +++--- docs/structures_Payment.js.html | 6 +++--- docs/structures_PrivateChat.js.html | 6 +++--- docs/structures_PrivateContact.js.html | 6 +++--- docs/structures_Product.js.html | 6 +++--- docs/structures_ProductMetadata.js.html | 6 +++--- docs/util_Constants.js.html | 6 +++--- docs/util_InterfaceController.js.html | 6 +++--- docs/util_Util.js.html | 6 +++--- package.json | 2 +- 48 files changed, 144 insertions(+), 144 deletions(-) diff --git a/docs/Base.html b/docs/Base.html index d7a1f13..9e3376a 100644 --- a/docs/Base.html +++ b/docs/Base.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Base + whatsapp-web.js 1.15.8 » Class: Base @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@
diff --git a/docs/BusinessContact.html b/docs/BusinessContact.html index d399c19..2a3b10c 100644 --- a/docs/BusinessContact.html +++ b/docs/BusinessContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: BusinessContact + whatsapp-web.js 1.15.8 » Class: BusinessContact @@ -15,7 +15,7 @@ @@ -314,7 +314,7 @@
diff --git a/docs/Buttons.html b/docs/Buttons.html index 3974cb0..642cdc3 100644 --- a/docs/Buttons.html +++ b/docs/Buttons.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Buttons + whatsapp-web.js 1.15.8 » Class: Buttons @@ -15,7 +15,7 @@ @@ -234,7 +234,7 @@ Returns: [{ buttonId:'customId',buttonText:{'displayText':&#
diff --git a/docs/Call.html b/docs/Call.html index 09556f6..de50a16 100644 --- a/docs/Call.html +++ b/docs/Call.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Call + whatsapp-web.js 1.15.8 » Class: Call @@ -15,7 +15,7 @@ @@ -144,7 +144,7 @@
diff --git a/docs/Chat.html b/docs/Chat.html index 13f4f2a..1a9cd4b 100644 --- a/docs/Chat.html +++ b/docs/Chat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Chat + whatsapp-web.js 1.15.8 » Class: Chat @@ -15,7 +15,7 @@ @@ -483,7 +483,7 @@
diff --git a/docs/Client.html b/docs/Client.html index d2d1b23..0d30a80 100644 --- a/docs/Client.html +++ b/docs/Client.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Client + whatsapp-web.js 1.15.8 » Class: Client @@ -15,7 +15,7 @@ @@ -2416,7 +2416,7 @@
diff --git a/docs/Client.js.html b/docs/Client.js.html index 0cfcbaf..74260b7 100644 --- a/docs/Client.js.html +++ b/docs/Client.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: Client.js + whatsapp-web.js 1.15.8 » Source: Client.js @@ -15,7 +15,7 @@ @@ -1069,7 +1069,7 @@ module.exports = Client;
diff --git a/docs/ClientInfo.html b/docs/ClientInfo.html index 11c25bc..15bb01e 100644 --- a/docs/ClientInfo.html +++ b/docs/ClientInfo.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: ClientInfo + whatsapp-web.js 1.15.8 » Class: ClientInfo @@ -15,7 +15,7 @@ @@ -238,7 +238,7 @@
diff --git a/docs/Contact.html b/docs/Contact.html index e13b9ab..d726f30 100644 --- a/docs/Contact.html +++ b/docs/Contact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Contact + whatsapp-web.js 1.15.8 » Class: Contact @@ -15,7 +15,7 @@ @@ -281,7 +281,7 @@
diff --git a/docs/GroupChat.html b/docs/GroupChat.html index 013d8cd..9918ae8 100644 --- a/docs/GroupChat.html +++ b/docs/GroupChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: GroupChat + whatsapp-web.js 1.15.8 » Class: GroupChat @@ -15,7 +15,7 @@ @@ -921,7 +921,7 @@
diff --git a/docs/GroupNotification.html b/docs/GroupNotification.html index 66c98e7..51b4c41 100644 --- a/docs/GroupNotification.html +++ b/docs/GroupNotification.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: GroupNotification + whatsapp-web.js 1.15.8 » Class: GroupNotification @@ -15,7 +15,7 @@ @@ -233,7 +233,7 @@
diff --git a/docs/InterfaceController.html b/docs/InterfaceController.html index ed99c12..1d18650 100644 --- a/docs/InterfaceController.html +++ b/docs/InterfaceController.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: InterfaceController + whatsapp-web.js 1.15.8 » Class: InterfaceController @@ -15,7 +15,7 @@ @@ -382,7 +382,7 @@
diff --git a/docs/Label.html b/docs/Label.html index 10c052a..0f95982 100644 --- a/docs/Label.html +++ b/docs/Label.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Label + whatsapp-web.js 1.15.8 » Class: Label @@ -15,7 +15,7 @@ @@ -163,7 +163,7 @@
diff --git a/docs/List.html b/docs/List.html index 8c5a7ea..f5aa6f7 100644 --- a/docs/List.html +++ b/docs/List.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: List + whatsapp-web.js 1.15.8 » Class: List @@ -15,7 +15,7 @@ @@ -256,7 +256,7 @@ Returns: [{'title':'sectionTitle','rows':[{'r
diff --git a/docs/Location.html b/docs/Location.html index e62ba9b..ece044c 100644 --- a/docs/Location.html +++ b/docs/Location.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Location + whatsapp-web.js 1.15.8 » Class: Location @@ -15,7 +15,7 @@ @@ -149,7 +149,7 @@
diff --git a/docs/Message.html b/docs/Message.html index 7c9d916..f3d9578 100644 --- a/docs/Message.html +++ b/docs/Message.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Message + whatsapp-web.js 1.15.8 » Class: Message @@ -15,7 +15,7 @@ @@ -580,7 +580,7 @@
diff --git a/docs/MessageMedia.html b/docs/MessageMedia.html index df6284b..781b468 100644 --- a/docs/MessageMedia.html +++ b/docs/MessageMedia.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: MessageMedia + whatsapp-web.js 1.15.8 » Class: MessageMedia @@ -15,7 +15,7 @@ @@ -343,7 +343,7 @@
diff --git a/docs/Order.html b/docs/Order.html index 471e31f..96d69db 100644 --- a/docs/Order.html +++ b/docs/Order.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Order + whatsapp-web.js 1.15.8 » Class: Order @@ -15,7 +15,7 @@ @@ -102,7 +102,7 @@
diff --git a/docs/PrivateChat.html b/docs/PrivateChat.html index 51c8549..fc3cb17 100644 --- a/docs/PrivateChat.html +++ b/docs/PrivateChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: PrivateChat + whatsapp-web.js 1.15.8 » Class: PrivateChat @@ -15,7 +15,7 @@ @@ -519,7 +519,7 @@
diff --git a/docs/PrivateContact.html b/docs/PrivateContact.html index 14db873..1f3f4c6 100644 --- a/docs/PrivateContact.html +++ b/docs/PrivateContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: PrivateContact + whatsapp-web.js 1.15.8 » Class: PrivateContact @@ -15,7 +15,7 @@ @@ -307,7 +307,7 @@
diff --git a/docs/Product.html b/docs/Product.html index 083153e..40a21e4 100644 --- a/docs/Product.html +++ b/docs/Product.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Product + whatsapp-web.js 1.15.8 » Class: Product @@ -15,7 +15,7 @@ @@ -127,7 +127,7 @@
diff --git a/docs/Util.html b/docs/Util.html index e7ff1f4..c0d09eb 100644 --- a/docs/Util.html +++ b/docs/Util.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Class: Util + whatsapp-web.js 1.15.8 » Class: Util @@ -15,7 +15,7 @@ @@ -243,7 +243,7 @@
diff --git a/docs/global.html b/docs/global.html index 6b9ae8d..79f79c6 100644 --- a/docs/global.html +++ b/docs/global.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Globals + whatsapp-web.js 1.15.8 » Globals @@ -15,7 +15,7 @@ @@ -1980,7 +1980,7 @@
diff --git a/docs/index.html b/docs/index.html index 64fc62f..cbadf1a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Home + whatsapp-web.js 1.15.8 » Home @@ -15,7 +15,7 @@ @@ -27,11 +27,11 @@
-

npm Depfu WhatsApp_Web 2.2202.12 Discord Chat

+

npm Depfu WhatsApp_Web 2.2206.5 Discord Chat

whatsapp-web.js

A WhatsApp API client that connects through the WhatsApp Web browser app

It uses Puppeteer to run a real instance of Whatsapp Web to avoid getting blocked.

@@ -3031,7 +3031,7 @@ client.initialize();
diff --git a/docs/structures_Base.js.html b/docs/structures_Base.js.html index 27d1de8..c0e7e75 100644 --- a/docs/structures_Base.js.html +++ b/docs/structures_Base.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Base.js + whatsapp-web.js 1.15.8 » Source: structures/Base.js @@ -15,7 +15,7 @@ @@ -60,7 +60,7 @@ module.exports = Base;
diff --git a/docs/structures_BusinessContact.js.html b/docs/structures_BusinessContact.js.html index 7a042a2..2f5f4e5 100644 --- a/docs/structures_BusinessContact.js.html +++ b/docs/structures_BusinessContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/BusinessContact.js + whatsapp-web.js 1.15.8 » Source: structures/BusinessContact.js @@ -15,7 +15,7 @@ @@ -59,7 +59,7 @@ module.exports = BusinessContact;
diff --git a/docs/structures_Buttons.js.html b/docs/structures_Buttons.js.html index d7ac74e..822ba2b 100644 --- a/docs/structures_Buttons.js.html +++ b/docs/structures_Buttons.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Buttons.js + whatsapp-web.js 1.15.8 » Source: structures/Buttons.js @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@ module.exports = Buttons;
diff --git a/docs/structures_Call.js.html b/docs/structures_Call.js.html index ddef6ac..6baa364 100644 --- a/docs/structures_Call.js.html +++ b/docs/structures_Call.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Call.js + whatsapp-web.js 1.15.8 » Source: structures/Call.js @@ -15,7 +15,7 @@ @@ -106,7 +106,7 @@ module.exports = Call;
diff --git a/docs/structures_Chat.js.html b/docs/structures_Chat.js.html index 08d131b..735b3b3 100644 --- a/docs/structures_Chat.js.html +++ b/docs/structures_Chat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Chat.js + whatsapp-web.js 1.15.8 » Source: structures/Chat.js @@ -15,7 +15,7 @@ @@ -290,7 +290,7 @@ module.exports = Chat;
diff --git a/docs/structures_ClientInfo.js.html b/docs/structures_ClientInfo.js.html index 61cc3c3..7ce31e0 100644 --- a/docs/structures_ClientInfo.js.html +++ b/docs/structures_ClientInfo.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/ClientInfo.js + whatsapp-web.js 1.15.8 » Source: structures/ClientInfo.js @@ -15,7 +15,7 @@ @@ -108,7 +108,7 @@ module.exports = ClientInfo;
diff --git a/docs/structures_Contact.js.html b/docs/structures_Contact.js.html index c16dd8d..604631f 100644 --- a/docs/structures_Contact.js.html +++ b/docs/structures_Contact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Contact.js + whatsapp-web.js 1.15.8 » Source: structures/Contact.js @@ -15,7 +15,7 @@ @@ -236,7 +236,7 @@ module.exports = Contact;
diff --git a/docs/structures_GroupChat.js.html b/docs/structures_GroupChat.js.html index fd3eadc..8035ac8 100644 --- a/docs/structures_GroupChat.js.html +++ b/docs/structures_GroupChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/GroupChat.js + whatsapp-web.js 1.15.8 » Source: structures/GroupChat.js @@ -15,7 +15,7 @@ @@ -239,7 +239,7 @@ module.exports = GroupChat;
diff --git a/docs/structures_GroupNotification.js.html b/docs/structures_GroupNotification.js.html index 334bd35..fd11099 100644 --- a/docs/structures_GroupNotification.js.html +++ b/docs/structures_GroupNotification.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/GroupNotification.js + whatsapp-web.js 1.15.8 » Source: structures/GroupNotification.js @@ -15,7 +15,7 @@ @@ -143,7 +143,7 @@ module.exports = GroupNotification;
diff --git a/docs/structures_Label.js.html b/docs/structures_Label.js.html index d9d0255..85456e3 100644 --- a/docs/structures_Label.js.html +++ b/docs/structures_Label.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Label.js + whatsapp-web.js 1.15.8 » Source: structures/Label.js @@ -15,7 +15,7 @@ @@ -88,7 +88,7 @@ module.exports = Label;
diff --git a/docs/structures_List.js.html b/docs/structures_List.js.html index 22b9506..425b918 100644 --- a/docs/structures_List.js.html +++ b/docs/structures_List.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/List.js + whatsapp-web.js 1.15.8 » Source: structures/List.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@ module.exports = List;
diff --git a/docs/structures_Location.js.html b/docs/structures_Location.js.html index a8df945..5b97b5d 100644 --- a/docs/structures_Location.js.html +++ b/docs/structures_Location.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Location.js + whatsapp-web.js 1.15.8 » Source: structures/Location.js @@ -15,7 +15,7 @@ @@ -71,7 +71,7 @@ module.exports = Location;
diff --git a/docs/structures_Message.js.html b/docs/structures_Message.js.html index 0b980aa..678b129 100644 --- a/docs/structures_Message.js.html +++ b/docs/structures_Message.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Message.js + whatsapp-web.js 1.15.8 » Source: structures/Message.js @@ -15,7 +15,7 @@ @@ -525,7 +525,7 @@ module.exports = Message;
diff --git a/docs/structures_MessageMedia.js.html b/docs/structures_MessageMedia.js.html index a77e3b4..29ef8ea 100644 --- a/docs/structures_MessageMedia.js.html +++ b/docs/structures_MessageMedia.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/MessageMedia.js + whatsapp-web.js 1.15.8 » Source: structures/MessageMedia.js @@ -15,7 +15,7 @@ @@ -142,7 +142,7 @@ module.exports = MessageMedia;
diff --git a/docs/structures_Order.js.html b/docs/structures_Order.js.html index 282ee27..30eeaad 100644 --- a/docs/structures_Order.js.html +++ b/docs/structures_Order.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Order.js + whatsapp-web.js 1.15.8 » Source: structures/Order.js @@ -15,7 +15,7 @@ @@ -90,7 +90,7 @@ module.exports = Order;
diff --git a/docs/structures_Payment.js.html b/docs/structures_Payment.js.html index 409a704..9a5df3a 100644 --- a/docs/structures_Payment.js.html +++ b/docs/structures_Payment.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Payment.js + whatsapp-web.js 1.15.8 » Source: structures/Payment.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@ module.exports = Payment;
diff --git a/docs/structures_PrivateChat.js.html b/docs/structures_PrivateChat.js.html index a880fad..34c4b85 100644 --- a/docs/structures_PrivateChat.js.html +++ b/docs/structures_PrivateChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/PrivateChat.js + whatsapp-web.js 1.15.8 » Source: structures/PrivateChat.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@ module.exports = PrivateChat;
diff --git a/docs/structures_PrivateContact.js.html b/docs/structures_PrivateContact.js.html index d442ce8..f8abb71 100644 --- a/docs/structures_PrivateContact.js.html +++ b/docs/structures_PrivateContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/PrivateContact.js + whatsapp-web.js 1.15.8 » Source: structures/PrivateContact.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@ module.exports = PrivateContact;
diff --git a/docs/structures_Product.js.html b/docs/structures_Product.js.html index 6141be0..45bc859 100644 --- a/docs/structures_Product.js.html +++ b/docs/structures_Product.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/Product.js + whatsapp-web.js 1.15.8 » Source: structures/Product.js @@ -15,7 +15,7 @@ @@ -106,7 +106,7 @@ module.exports = Product;
diff --git a/docs/structures_ProductMetadata.js.html b/docs/structures_ProductMetadata.js.html index 8908cd0..e54cdb5 100644 --- a/docs/structures_ProductMetadata.js.html +++ b/docs/structures_ProductMetadata.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: structures/ProductMetadata.js + whatsapp-web.js 1.15.8 » Source: structures/ProductMetadata.js @@ -15,7 +15,7 @@ @@ -63,7 +63,7 @@ module.exports = ProductMetadata;
diff --git a/docs/util_Constants.js.html b/docs/util_Constants.js.html index 8c6018f..0b0d6b9 100644 --- a/docs/util_Constants.js.html +++ b/docs/util_Constants.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: util/Constants.js + whatsapp-web.js 1.15.8 » Source: util/Constants.js @@ -15,7 +15,7 @@ @@ -200,7 +200,7 @@ exports.MessageAck = {
diff --git a/docs/util_InterfaceController.js.html b/docs/util_InterfaceController.js.html index 55442c5..c29caf9 100644 --- a/docs/util_InterfaceController.js.html +++ b/docs/util_InterfaceController.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: util/InterfaceController.js + whatsapp-web.js 1.15.8 » Source: util/InterfaceController.js @@ -15,7 +15,7 @@ @@ -160,7 +160,7 @@ module.exports = InterfaceController;
diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index be575cf..f949734 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.7 » Source: util/Util.js + whatsapp-web.js 1.15.8 » Source: util/Util.js @@ -15,7 +15,7 @@ @@ -242,7 +242,7 @@ module.exports = Util;
diff --git a/package.json b/package.json index 96178fa..2ed4f90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "whatsapp-web.js", - "version": "1.15.7", + "version": "1.15.8", "description": "Library for interacting with the WhatsApp Web API ", "main": "./index.js", "typings": "./index.d.ts", From 0d55d408850ef5214fde751d8b55af42fd70aef1 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 27 Feb 2022 14:51:08 -0800 Subject: [PATCH 08/50] feat: Multi-device support (#889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :ambulance: Added ready selector for multi-device * SendMessage fix * File management system and some fixes * cleanup * cleanup again * eslint * critical fix for reloading the same session * Checking for valid folder name (regex) * ESLint hotfix (regex escapes) * Typings cleanup * cleanup listener * Multi-device Branch merge (#888) * Duplicate * qr fix and allow non-beta users to connect * urgent: selector fix * urgent: qr timeout fix * fix * Updated type so no TS error when sending list/buttons * Update index.d.ts * fix QueryExist for Multidevice (#928) * creates isRegisteredUserBeta * fix QueryExist * fix Error: GROUP_JID: invalid jid type: Not an instance of WID issue (#926) * fix Error: GROUP_JID: invalid jid type: Not an instance of WID issue * clean code * Cleanup * Fix for update chrome error * ESLint fix * :red_light: fix for RMDIR * Update README.md * Update README.md * fix: getProfilePicUrl fix by victormga (#941) * fix: MD presence available/unavailable (#942) * delete session when appropriate & fix for SW * ignore QR timeout errors * Presence and ChatState updates working for MD+Non-MD * shell uses new session storage * lint fix * support session.json-based auth for non-md * md fix * md fix * fix shell clientId * remove exclusive mocha test * make linkPreview default to false * remove ignored errors on getQuotedMessage * fix: dont modify existing this.options.puppeteer object * tests work with new dir auth * remove exclusive test * fixes and tests for group creation and participant functions * remove unused function * wip fix group settings functions * isRegisteredUser && getNumberId hotFix (#955) * isRegisteredUser && getNumberId hotFix A fix for client.isRegisteredUser and client.getNumberId. Use for reference or if you are stuck with MD and NEEDS this function. Problably Whatsapp will break this in a couple weeks * fix for non-md Co-authored-by: Rajeh Taher * Fix WA 2.2146.9 MD + victormga branch (#991) * qrcode now uses observers instead of timeout * automatic auth/qrcode detection * Fix WA 2.2146.9 MD Got from github:victormga/whatsapp-web.js#multidevice maybe it's behind pedro branch Co-authored-by: victormga * fix * fix* * getnumberid to multidevice (#1027) * getNumberId to main isRegisteredUser && getNumberId hotFix #955 To main * Update Client.js Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com> * Update Client.js * Message.raw() (#1005) * Message.raw() * i just noticed * Update index.d.ts * Update index.d.ts * Update Message.js * Get rid of sharp now!!!!!!!! (#1045) * commit 1 * finally, gotten rid of sharp * pckg.json * service worker fix & disableMessage option * typings * Update example.js * clear session system * Update Client.js * Update Client.js * Fix accepting group private invite (#1094) Co-authored-by: github-actions[bot] * [MD] Add getCommonGroups with specific user. (#1097) * Add getCommonGroups with specific user. * Fix * Fix * Fix Co-authored-by: github-actions[bot] * Fix getCommonGroups. (#1122) * Fix of Unexpected identifier async destroy() (#1123) * Fix of Unexpected identifier async destroy() * Fix made in #1107 * Temporary fix for "Sticker" module * some really quick changes * Update Injected.js * Update Injected.js * Update index.d.ts * fix: getNumberId Solved (#1142) * getNumberId Solved * isRegisteredUser Solved * formmated * Apply suggestions from code review * Update src/util/Injected.js Co-authored-by: Rajeh Taher * Fix: "Chrome user data dir was not found ..." fixes the error caused by puppeteer. * Update Client.js (#1154) * fix: getNumberId and isRegisteredUser (#1159) * fix: getNumberId and isRegisteredUser * Apply suggestions from code review Co-authored-by: Rajeh Taher * Update client.js * Update Injected.js * Update Client.js * Update index.d.ts * Update Client.js * Update Client.js * fix lint indentation * fix auth_failure event for non-md, tests * fix setting group subject * fix finding Label module * set remember-me after clearing localStorage * fix: send messages to groups correctly on MD, use new ID format * fix setting / getting contact status * fix msg.getInfo, add message tests * fix group settings functions * fix set group description, handle errors in setSubject * fix group invite functions * fix leaving group * bring back phone info for non-md users * remove unused option, update typings * add back jsdoc for qr event * fix setting sticker metadata, clean up sticker functions * rawData is a get only property * fix and simplify getNumberId/isRegisteredUser * fix getInviteInfo * setDisplayName returns bool, not yet implemented for md * fix: stream module (#1241) * linkPreview has no effect on MD, return default to true * fix: del linkPreview option on md * cleanup, types and docs updates * update readmes / test notes * remove DS_Store * DS_Store in gitignore * test stability (timeouts/sleeps) Co-authored-by: Rajeh Taher Co-authored-by: Gustavo B <52040719+Gugabit@users.noreply.github.com> Co-authored-by: Maikel Ortega Hernández Co-authored-by: victormga Co-authored-by: Pedro Lopez Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Co-authored-by: gon <68490103+nekiak@users.noreply.github.com> Co-authored-by: Alon Schwartzblat <63599777+Schwartzblat@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Šebestíček <44745014+SebestikCZ@users.noreply.github.com> Co-authored-by: Emmanuel Anaya Luna <38712443+KeruMx@users.noreply.github.com> Co-authored-by: L337C0D3R <51872799+L337C0D3R@users.noreply.github.com> Co-authored-by: Reni Delonzek --- .env.example | 5 +- .gitignore | 9 +- example.js | 35 +-- index.d.ts | 77 +++++-- package.json | 6 +- shell.js | 13 +- src/Client.js | 398 +++++++++++++++++++++-------------- src/structures/ClientInfo.js | 13 +- src/structures/Contact.js | 11 +- src/structures/GroupChat.js | 105 +++++---- src/structures/Message.js | 83 +++++--- src/util/Constants.js | 7 +- src/util/Injected.js | 188 ++++++++++------- src/util/Util.js | 54 ++--- tests/README.md | 10 +- tests/client.js | 277 +++++++++++++----------- tests/helper.js | 40 +++- tests/structures/chat.js | 15 +- tests/structures/group.js | 227 ++++++++++++++++++++ tests/structures/message.js | 112 ++++++++++ 20 files changed, 1132 insertions(+), 553 deletions(-) create mode 100644 tests/structures/group.js create mode 100644 tests/structures/message.js diff --git a/.env.example b/.env.example index 6cd9eb9..3add97c 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ -WWEBJS_TEST_SESSION_PATH=test_session.json -WWEBJS_TEST_REMOTE_ID=XXXXXXXXXX@c.us \ No newline at end of file +WWEBJS_TEST_REMOTE_ID=XXXXXXXXXX@c.us +WWEBJS_TEST_CLIENT_ID=authenticated +WWEBJS_TEST_MD=1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f0b9c4f..73c67dc 100644 --- a/.gitignore +++ b/.gitignore @@ -64,8 +64,13 @@ typings/ # next.js build output .next -# macOS Thumbnails +# macOS ._* +.DS_Store # Test sessions -*session.json \ No newline at end of file +*session.json + +# user data +WWebJS/ +userDataDir/ \ No newline at end of file diff --git a/example.js b/example.js index 4958e87..bc3388e 100644 --- a/example.js +++ b/example.js @@ -1,15 +1,9 @@ -const fs = require('fs'); const { Client, Location, List, Buttons } = require('./index'); -const SESSION_FILE_PATH = './session.json'; -let sessionCfg; -if (fs.existsSync(SESSION_FILE_PATH)) { - sessionCfg = require(SESSION_FILE_PATH); -} - -const client = new Client({ puppeteer: { headless: false }, session: sessionCfg }); -// You can use an existing session and avoid scanning a QR code by adding a "session" object to the client options. -// This object must include WABrowserId, WASecretBundle, WAToken1 and WAToken2. +const client = new Client({ + clientId: 'example', + puppeteer: { headless: false } +}); // You also could connect to an existing instance of a browser // { @@ -25,18 +19,12 @@ client.on('qr', (qr) => { console.log('QR RECEIVED', qr); }); -client.on('authenticated', (session) => { - console.log('AUTHENTICATED', session); - sessionCfg=session; - fs.writeFile(SESSION_FILE_PATH, JSON.stringify(session), function (err) { - if (err) { - console.error(err); - } - }); +client.on('authenticated', () => { + console.log('AUTHENTICATED'); }); client.on('auth_failure', msg => { - // Fired if session restore was unsuccessfull + // Fired if session restore was unsuccessful console.error('AUTHENTICATION FAILURE', msg); }); @@ -124,9 +112,8 @@ client.on('message', async msg => { client.sendMessage(msg.from, ` *Connection info* User name: ${info.pushname} - My number: ${info.me.user} + My number: ${info.wid.user} Platform: ${info.platform} - WhatsApp version: ${info.phone.wa_version} `); } else if (msg.body === '!mediainfo' && msg.hasMedia) { const attachmentData = await msg.downloadMedia(); @@ -267,12 +254,6 @@ client.on('group_update', (notification) => { console.log('update', notification); }); -client.on('change_battery', (batteryInfo) => { - // Battery percentage for attached device has changed - const { battery, plugged } = batteryInfo; - console.log(`Battery: ${battery}% - Charging? ${plugged}`); -}); - client.on('change_state', state => { console.log('CHANGE STATE', state ); }); diff --git a/index.d.ts b/index.d.ts index 7039752..c3d1ab1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -84,6 +84,9 @@ declare namespace WAWebJS { /** Returns the contact ID's profile picture URL, if privacy settings allow it */ getProfilePicUrl(contactId: string): Promise + /** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */ + getCommonGroups(contactId: string): Promise + /** Gets the current connection state for the client */ getState(): Promise @@ -118,6 +121,9 @@ declare namespace WAWebJS { /** Marks the client as online */ sendPresenceAvailable(): Promise + /** Marks the client as offline */ + sendPresenceUnavailable(): Promise + /** Mark as seen for the Chat */ sendSeen(chatId: string): Promise @@ -134,7 +140,7 @@ declare namespace WAWebJS { * Sets the current user's display name * @param displayName New display name */ - setDisplayName(displayName: string): Promise + setDisplayName(displayName: string): Promise /** Changes and returns the archive state of the Chat */ unarchiveChat(chatId: string): Promise @@ -150,11 +156,17 @@ declare namespace WAWebJS { /** Emitted when authentication is successful */ on(event: 'authenticated', listener: ( - /** Object containing session information. Can be used to restore the session */ - session: ClientSession + /** + * Object containing session information. Can be used to restore the session + * @deprecated + */ + session?: ClientSession ) => void): this - /** Emitted when the battery percentage for the attached device changes */ + /** + * Emitted when the battery percentage for the attached device changes + * @deprecated + */ on(event: 'change_battery', listener: (batteryInfo: BatteryInfo) => void): this /** Emitted when the connection state changes */ @@ -249,14 +261,12 @@ declare namespace WAWebJS { /** Current connection information */ export interface ClientInfo { - /** - * Current user ID - * @deprecated Use .wid instead - */ - me: ContactId /** Current user ID */ wid: ContactId - /** Information about the phone this client is connected to */ + /** + * Information about the phone this client is connected to. Not available in multi-device. + * @deprecated + */ phone: ClientInfoPhone /** Platform the phone is running on */ platform: string @@ -267,7 +277,10 @@ declare namespace WAWebJS { getBatteryStatus: () => Promise } - /** Information about the phone this client is connected to */ + /** + * Information about the phone this client is connected to + * @deprecated + */ export interface ClientInfoPhone { /** WhatsApp Version running on the phone */ wa_version: string @@ -300,8 +313,19 @@ declare namespace WAWebJS { /** Restart client with a new session (i.e. use null 'session' var) if authentication fails * @default false */ restartOnAuthFail?: boolean - /** Whatsapp session to restore. If not set, will start a new session */ + /** + * Enable authentication via a `session` option. + * @deprecated Will be removed in a future release + */ + useDeprecatedSessionAuth?: boolean + /** + * WhatsApp session to restore. If not set, will start a new session + * @deprecated Set `useDeprecatedSessionAuth: true` to enable. This auth method is not supported by MultiDevice and will be removed in a future release. + */ session?: ClientSession + /** Client id to distinguish instances if you are using multiple, otherwise keep empty if you are using only one instance + * @default '' */ + clientId: string /** If another whatsapp web session is detected (another browser), take over the session in the current browser * @default false */ takeoverOnConflict?: boolean, @@ -314,9 +338,15 @@ declare namespace WAWebJS { /** Ffmpeg path to use when formating videos to webp while sending stickers * @default 'ffmpeg' */ ffmpegPath?: string + /** Path to place session objects in + @default './WWebJS' */ + dataPath?: string } - /** Represents a Whatsapp client session */ + /** + * Represents a WhatsApp client session + * @deprecated + */ export interface ClientSession { WABrowserId: string, WASecretBundle: string, @@ -324,6 +354,9 @@ declare namespace WAWebJS { WAToken2: string, } + /** + * @deprecated + */ export interface BatteryInfo { /** The current battery percentage */ battery: number, @@ -608,6 +641,13 @@ declare namespace WAWebJS { selectedButtonId?: string, /** Selected list row ID */ selectedRowId?: string, + /** Returns message in a raw format */ + rawData: object, + /* + * Reloads this Message object's data in-place with the latest values from WhatsApp Web. + * Note that the Message must still be in the web app cache for this to work, otherwise will return null. + */ + reload: () => Promise, /** Accept the Group V4 Invite in message */ acceptGroupV4Invite: () => Promise<{status: number}>, /** Deletes the message from the chat */ @@ -679,7 +719,7 @@ declare namespace WAWebJS { /** Options for sending a message */ export interface MessageSendOptions { - /** Show links preview */ + /** Show links preview. Has no effect on multi-device accounts. */ linkPreview?: boolean /** Send audio as voice message */ sendAudioAsVoice?: boolean @@ -741,7 +781,7 @@ declare namespace WAWebJS { static fromUrl: (url: string, options?: MediaFromURLOptions) => Promise } - export type MessageContent = string | MessageMedia | Location | Contact | Contact[] | List | Buttons + export type MessageContent = string | MessageMedia | Location | Contact | Contact[] | List | Buttons /** * Represents a Contact on WhatsApp @@ -834,6 +874,9 @@ declare namespace WAWebJS { /** Gets the Contact's current "about" info. Returns null if you don't have permission to read their status. */ getAbout: () => Promise, + + /** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */ + getCommonGroups: () => Promise } @@ -1011,9 +1054,9 @@ declare namespace WAWebJS { /** Demotes participants by IDs to regular users */ demoteParticipants: ChangeParticipantsPermisions; /** Updates the group subject */ - setSubject: (subject: string) => Promise; + setSubject: (subject: string) => Promise; /** Updates the group description */ - setDescription: (description: string) => Promise; + setDescription: (description: string) => Promise; /** Updates the group settings to only allow admins to send messages * @param {boolean} [adminsOnly=true] Enable or disable this option * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions. diff --git a/package.json b/package.json index 2ed4f90..093758c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "./index.js", "typings": "./index.d.ts", "scripts": { - "test": "mocha tests --recursive", + "test": "mocha tests --recursive --timeout 5000", "test-single": "mocha", "shell": "node --experimental-repl-await ./shell.js", "generate-docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose" @@ -35,12 +35,12 @@ "mime": "^3.0.0", "node-fetch": "^2.6.5", "node-webpmux": "^3.1.0", - "puppeteer": "^13.0.0", - "sharp": "^0.28.3" + "puppeteer": "^13.0.0" }, "devDependencies": { "@types/node-fetch": "^2.5.12", "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", "dotenv": "^16.0.0", "eslint": "^8.4.1", "eslint-plugin-mocha": "^10.0.3", diff --git a/shell.js b/shell.js index 10e44d0..4f94c35 100644 --- a/shell.js +++ b/shell.js @@ -7,19 +7,12 @@ */ const repl = require('repl'); -const fs = require('fs'); const { Client } = require('./index'); -const SESSION_FILE_PATH = './session.json'; -let sessionCfg; -if (fs.existsSync(SESSION_FILE_PATH)) { - sessionCfg = require(SESSION_FILE_PATH); -} - const client = new Client({ puppeteer: { headless: false }, - session: sessionCfg + clientId: 'shell' }); console.log('Initializing...'); @@ -30,6 +23,10 @@ client.on('qr', () => { console.log('Please scan the QR code on the browser.'); }); +client.on('authenticated', (session) => { + console.log(JSON.stringify(session)); +}); + client.on('ready', () => { const shell = repl.start('wwebjs> '); shell.context.client = client; diff --git a/src/Client.js b/src/Client.js index 27c2079..5f908e0 100644 --- a/src/Client.js +++ b/src/Client.js @@ -1,9 +1,10 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); const EventEmitter = require('events'); const puppeteer = require('puppeteer'); const moduleRaid = require('@pedroslopez/moduleraid/moduleraid'); -const jsQR = require('jsqr'); const Util = require('./util/Util'); const InterfaceController = require('./util/InterfaceController'); @@ -11,27 +12,24 @@ const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constan const { ExposeStore, LoadUtils } = require('./util/Injected'); const ChatFactory = require('./factories/ChatFactory'); const ContactFactory = require('./factories/ContactFactory'); -const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification , Label, Call, Buttons, List} = require('./structures'); +const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List } = require('./structures'); /** * Starting point for interacting with the WhatsApp Web API * @extends {EventEmitter} * @param {object} options - Client options * @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer * @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ - * @param {number} options.qrRefreshIntervalMs - Refresh interval for qr code (how much time to wait before checking if the qr code has changed) - * @param {number} options.qrTimeoutMs - Timeout for qr code selector in puppeteer * @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails - * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session - * @param {string} options.session.WABrowserId - * @param {string} options.session.WASecretBundle - * @param {string} options.session.WAToken1 - * @param {string} options.session.WAToken2 + * @param {boolean} options.useDeprecatedSessionAuth - Enable JSON-based authentication. This is deprecated due to not being supported by MultiDevice, and will be removed in a future version. + * @param {object} options.session - This is deprecated due to not being supported by MultiDevice, and will be removed in a future version. * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session + * @param {string} options.dataPath - Change the default path for saving session files, default is: "./WWebJS/" * @param {string} options.userAgent - User agent to use in puppeteer * @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy. + * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance * * @fires Client#qr * @fires Client#authenticated @@ -48,7 +46,6 @@ const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification * @fires Client#group_update * @fires Client#disconnected * @fires Client#change_state - * @fires Client#change_battery */ class Client extends EventEmitter { constructor(options = {}) { @@ -56,6 +53,19 @@ class Client extends EventEmitter { this.options = Util.mergeDefault(DefaultOptions, options); + this.id = this.options.clientId; + + // eslint-disable-next-line no-useless-escape + const foldernameRegex = /^(?!.{256,})(?!(aux|clock\$|con|nul|prn|com[1-9]|lpt[1-9])(?:$|\.))[^ ][ \.\w-$()+=[\];#@~,&']+[^\. ]$/i; + if (this.id && !foldernameRegex.test(this.id)) throw Error('Invalid client ID. Make sure you abide by the folder naming rules of your operating system.'); + + if (!this.options.useDeprecatedSessionAuth) { + this.dataDir = this.options.puppeteer.userDataDir; + const dirPath = path.join(process.cwd(), this.options.dataPath, this.id ? 'session-' + this.id : 'session'); + if (!this.dataDir) this.dataDir = dirPath; + fs.mkdirSync(this.dataDir, { recursive: true }); + } + this.pupBrowser = null; this.pupPage = null; @@ -67,39 +77,39 @@ class Client extends EventEmitter { */ async initialize() { let [browser, page] = [null, null]; - - if(this.options.puppeteer && this.options.puppeteer.browserWSEndpoint) { - browser = await puppeteer.connect(this.options.puppeteer); + + const puppeteerOpts = { + ...this.options.puppeteer, + userDataDir: this.options.useDeprecatedSessionAuth ? undefined : this.dataDir + }; + if (puppeteerOpts && puppeteerOpts.browserWSEndpoint) { + browser = await puppeteer.connect(puppeteerOpts); page = await browser.newPage(); } else { - browser = await puppeteer.launch(this.options.puppeteer); + browser = await puppeteer.launch(puppeteerOpts); page = (await browser.pages())[0]; } - + await page.setUserAgent(this.options.userAgent); this.pupBrowser = browser; this.pupPage = page; - // remember me - await page.evaluateOnNewDocument(() => { - localStorage.setItem('remember-me', 'true'); - }); + if (this.options.useDeprecatedSessionAuth && this.options.session) { + await page.evaluateOnNewDocument(session => { + if (document.referrer === 'https://whatsapp.com/') { + localStorage.clear(); + localStorage.setItem('WABrowserId', session.WABrowserId); + localStorage.setItem('WASecretBundle', session.WASecretBundle); + localStorage.setItem('WAToken1', session.WAToken1); + localStorage.setItem('WAToken2', session.WAToken2); + } - if (this.options.session) { - await page.evaluateOnNewDocument( - session => { - if(document.referrer === 'https://whatsapp.com/') { - localStorage.clear(); - localStorage.setItem('WABrowserId', session.WABrowserId); - localStorage.setItem('WASecretBundle', session.WASecretBundle); - localStorage.setItem('WAToken1', session.WAToken1); - localStorage.setItem('WAToken2', session.WAToken2); - } - }, this.options.session); + localStorage.setItem('remember-me', 'true'); + }, this.options.session); } - if(this.options.bypassCSP) { + if (this.options.bypassCSP) { await page.setBypassCSP(true); } @@ -109,56 +119,55 @@ class Client extends EventEmitter { referer: 'https://whatsapp.com/' }); - const KEEP_PHONE_CONNECTED_IMG_SELECTOR = '[data-icon="intro-md-beta-logo-dark"], [data-icon="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"]'; + const INTRO_IMG_SELECTOR = '[data-testid="intro-md-beta-logo-dark"], [data-testid="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"]'; + const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas'; - if (this.options.session) { - // Check if session restore was successful - try { - await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: this.options.authTimeoutMs }); - } catch (err) { - if (err.name === 'TimeoutError') { - /** - * Emitted when there has been an error while trying to restore an existing session - * @event Client#auth_failure - * @param {string} message - */ - this.emit(Events.AUTHENTICATION_FAILURE, 'Unable to log in. Are the session details valid?'); - browser.close(); - if (this.options.restartOnAuthFail) { - // session restore failed so try again but without session to force new authentication - this.options.session = null; - this.initialize(); - } - return; + // Checks which selector appears first + const needAuthentication = await Promise.race([ + new Promise(resolve => { + page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs }) + .then(() => resolve(false)) + .catch((err) => resolve(err)); + }), + new Promise(resolve => { + page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs }) + .then(() => resolve(true)) + .catch((err) => resolve(err)); + }) + ]); + + // Checks if an error ocurred on the first found selector. The second will be discarded and ignored by .race; + if (needAuthentication instanceof Error) throw needAuthentication; + + // Scan-qrcode selector was found. Needs authentication + if (needAuthentication) { + if(this.options.session) { + /** + * Emitted when there has been an error while trying to restore an existing session + * @event Client#auth_failure + * @param {string} message + * @deprecated + */ + this.emit(Events.AUTHENTICATION_FAILURE, 'Unable to log in. Are the session details valid?'); + await this.destroy(); + if (this.options.restartOnAuthFail) { + // session restore failed so try again but without session to force new authentication + this.options.session = null; + return this.initialize(); } - - throw err; + return; } - } else { + const QR_CONTAINER = 'div[data-ref]'; + const QR_RETRY_BUTTON = 'div[data-ref] > span > button'; let qrRetries = 0; - - const getQrCode = async () => { - // Check if retry button is present - var QR_RETRY_SELECTOR = 'div[data-ref] > span > button'; - var qrRetry = await page.$(QR_RETRY_SELECTOR); - if (qrRetry) { - await qrRetry.click(); - } - - // Wait for QR Code - const QR_CANVAS_SELECTOR = 'canvas'; - await page.waitForSelector(QR_CANVAS_SELECTOR, { timeout: this.options.qrTimeoutMs }); - const qrImgData = await page.$eval(QR_CANVAS_SELECTOR, canvas => [].slice.call(canvas.getContext('2d').getImageData(0, 0, 264, 264).data)); - const qr = jsQR(qrImgData, 264, 264).data; - + await page.exposeFunction('qrChanged', async (qr) => { /** - * Emitted when the QR code is received + * Emitted when a QR code is received * @event Client#qr * @param {string} qr QR Code */ this.emit(Events.QR_RECEIVED, qr); - if (this.options.qrMaxRetries > 0) { qrRetries++; if (qrRetries > this.options.qrMaxRetries) { @@ -166,15 +175,39 @@ class Client extends EventEmitter { await this.destroy(); } } - }; - getQrCode(); - this._qrRefreshInterval = setInterval(getQrCode, this.options.qrRefreshIntervalMs); + }); + + await page.evaluate(function (selectors) { + const qr_container = document.querySelector(selectors.QR_CONTAINER); + window.qrChanged(qr_container.dataset.ref); + + const obs = new MutationObserver((muts) => { + muts.forEach(mut => { + // Listens to qr token change + if (mut.type === 'attributes' && mut.attributeName === 'data-ref') { + window.qrChanged(mut.target.dataset.ref); + } else + // Listens to retry button, when found, click it + if (mut.type === 'childList') { + const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON); + if (retry_button) retry_button.click(); + } + }); + }); + obs.observe(qr_container.parentElement, { + subtree: true, + childList: true, + attributes: true, + attributeFilter: ['data-ref'], + }); + }, { + QR_CONTAINER, + QR_RETRY_BUTTON + }); // Wait for code scan try { - await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: 0 }); - clearInterval(this._qrRefreshInterval); - this._qrRefreshInterval = undefined; + await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 }); } catch(error) { if ( error.name === 'ProtocolError' && @@ -187,32 +220,30 @@ class Client extends EventEmitter { throw error; } + } await page.evaluate(ExposeStore, moduleRaid.toString()); - - // Get session tokens - const localStorage = JSON.parse(await page.evaluate(() => { - return JSON.stringify(window.localStorage); - })); + let authEventPayload = undefined; + if (this.options.useDeprecatedSessionAuth) { + // Get session tokens + const localStorage = JSON.parse(await page.evaluate(() => { + return JSON.stringify(window.localStorage); + })); - const session = { - WABrowserId: localStorage.WABrowserId, - WASecretBundle: localStorage.WASecretBundle, - WAToken1: localStorage.WAToken1, - WAToken2: localStorage.WAToken2 - }; + authEventPayload = { + WABrowserId: localStorage.WABrowserId, + WASecretBundle: localStorage.WASecretBundle, + WAToken1: localStorage.WAToken1, + WAToken2: localStorage.WAToken2 + }; + } /** * Emitted when authentication is successful * @event Client#authenticated - * @param {object} session Object containing session information. Can be used to restore the session. - * @param {string} session.WABrowserId - * @param {string} session.WASecretBundle - * @param {string} session.WAToken1 - * @param {string} session.WAToken2 */ - this.emit(Events.AUTHENTICATED, session); + this.emit(Events.AUTHENTICATED, authEventPayload); // Check window.Store Injection await page.waitForFunction('window.Store != undefined'); @@ -221,8 +252,17 @@ class Client extends EventEmitter { return window.Store.Features.features.MD_BACKEND; }); - if(isMD) { - throw new Error('Multi-device is not yet supported by whatsapp-web.js. Please check out https://github.com/pedroslopez/whatsapp-web.js/pull/889 to follow the progress.'); + await page.evaluate(async () => { + // safely unregister service workers + const registrations = await navigator.serviceWorker.getRegistrations(); + for (let registration of registrations) { + registration.unregister(); + } + + }); + + if (this.options.useDeprecatedSessionAuth && isMD) { + throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.'); } //Load util functions (serializers, helper functions) @@ -234,7 +274,7 @@ class Client extends EventEmitter { * @type {ClientInfo} */ this.info = new ClientInfo(this, await page.evaluate(() => { - return window.Store.Conn.serialize(); + return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; })); // Add InterfaceController @@ -398,11 +438,12 @@ class Client extends EventEmitter { if (battery === undefined) return; /** - * Emitted when the battery percentage for the attached device changes + * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. * @event Client#change_battery * @param {object} batteryInfo * @param {number} batteryInfo.battery - The current battery percentage * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) + * @deprecated */ this.emit(Events.BATTERY_CHANGED, { battery, plugged }); }); @@ -421,14 +462,14 @@ class Client extends EventEmitter { * @param {boolean} call.webClientShouldHandle - If Waweb should handle * @param {object} call.participants - Participants */ - const cll = new Call(this,call); + const cll = new Call(this, call); this.emit(Events.INCOMING_CALL, cll); }); - + await page.evaluate(() => { window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); }); - window.Store.Msg.on('change:ack', (msg,ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); }); + window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); }); window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); }); @@ -466,9 +507,6 @@ class Client extends EventEmitter { * Closes the client */ async destroy() { - if (this._qrRefreshInterval) { - clearInterval(this._qrRefreshInterval); - } await this.pupBrowser.close(); } @@ -476,9 +514,13 @@ class Client extends EventEmitter { * Logs out the client, closing the current session */ async logout() { - return await this.pupPage.evaluate(() => { + await this.pupPage.evaluate(() => { return window.Store.AppState.logout(); }); + + if (this.dataDir) { + return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this.dataDir, { recursive: true }); + } } /** @@ -508,7 +550,7 @@ class Client extends EventEmitter { /** * Message options. * @typedef {Object} MessageSendOptions - * @property {boolean} [linkPreview=true] - Show links preview + * @property {boolean} [linkPreview=true] - Show links preview. Has no effect on multi-device accounts. * @property {boolean} [sendAudioAsVoice=false] - Send audio as voice message * @property {boolean} [sendVideoAsGif=false] - Send video as gif * @property {boolean} [sendMediaAsSticker=false] - Send media as a sticker @@ -558,34 +600,36 @@ class Client extends EventEmitter { } else if (content instanceof Location) { internalOptions.location = content; content = ''; - } else if(content instanceof Contact) { + } else if (content instanceof Contact) { internalOptions.contactCard = content.id._serialized; content = ''; - } else if(Array.isArray(content) && content.length > 0 && content[0] instanceof Contact) { + } else if (Array.isArray(content) && content.length > 0 && content[0] instanceof Contact) { internalOptions.contactCardList = content.map(contact => contact.id._serialized); content = ''; - } else if(content instanceof Buttons){ - if(content.type !== 'chat'){internalOptions.attachment = content.body;} + } else if (content instanceof Buttons) { + if (content.type !== 'chat') { internalOptions.attachment = content.body; } internalOptions.buttons = content; content = ''; - } else if(content instanceof List){ + } else if (content instanceof List) { internalOptions.list = content; content = ''; } if (internalOptions.sendMediaAsSticker && internalOptions.attachment) { - internalOptions.attachment = - await Util.formatToWebpSticker(internalOptions.attachment, { + internalOptions.attachment = await Util.formatToWebpSticker( + internalOptions.attachment, { name: options.stickerName, author: options.stickerAuthor, categories: options.stickerCategories - }); + }, this.pupPage + ); } const newMessage = await this.pupPage.evaluate(async (chatId, message, options, sendSeen) => { const chatWid = window.Store.WidFactory.createWid(chatId); const chat = await window.Store.Chat.find(chatWid); + if (sendSeen) { window.WWebJS.sendSeen(chatId); } @@ -672,7 +716,7 @@ class Client extends EventEmitter { */ async getInviteInfo(inviteCode) { return await this.pupPage.evaluate(inviteCode => { - return window.Store.Wap.groupInviteInfo(inviteCode); + return window.Store.InviteInfo.sendQueryGroupInvite(inviteCode); }, inviteCode); } @@ -691,25 +735,25 @@ class Client extends EventEmitter { /** * Accepts a private invitation to join a group - * @param {object} inviteV4 Invite V4 Info + * @param {object} inviteInfo Invite V4 Info * @returns {Promise} */ async acceptGroupV4Invite(inviteInfo) { - if(!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object'; + if (!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object'; if (inviteInfo.inviteCodeExp == 0) throw 'Expired invite code'; - return await this.pupPage.evaluate(async inviteInfo => { - let { groupId, fromId, inviteCode, inviteCodeExp, toId } = inviteInfo; - return await window.Store.Wap.acceptGroupV4Invite(groupId, fromId, inviteCode, String(inviteCodeExp), toId); + return this.pupPage.evaluate(async inviteInfo => { + let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo; + return await window.Store.JoinInviteV4.sendJoinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, fromId); }, inviteInfo); } - + /** * Sets the current user's status message * @param {string} status New status message */ async setStatus(status) { await this.pupPage.evaluate(async status => { - return await window.Store.Wap.sendSetStatus(status); + return await window.Store.StatusUtils.setMyStatus(status); }, status); } @@ -717,11 +761,22 @@ class Client extends EventEmitter { * Sets the current user's display name. * This is the name shown to WhatsApp users that have not added you as a contact beside your number in groups and in your profile. * @param {string} displayName New display name + * @returns {Promise} */ async setDisplayName(displayName) { - await this.pupPage.evaluate(async displayName => { - return await window.Store.Wap.setPushname(displayName); + const couldSet = await this.pupPage.evaluate(async displayName => { + if(!window.Store.Conn.canSetMyPushname()) return false; + + if(window.Store.Features.features.MD_BACKEND) { + // TODO + return false; + } else { + const res = await window.Store.Wap.setPushname(displayName); + return !res.status || res.status === 200; + } }, displayName); + + return couldSet; } /** @@ -740,7 +795,16 @@ class Client extends EventEmitter { */ async sendPresenceAvailable() { return await this.pupPage.evaluate(() => { - return window.Store.Wap.sendPresenceAvailable(); + return window.Store.PresenceUtils.sendPresenceAvailable(); + }); + } + + /** + * Marks the client as unavailable + */ + async sendPresenceUnavailable() { + return await this.pupPage.evaluate(() => { + return window.Store.PresenceUtils.sendPresenceUnavailable(); }); } @@ -847,12 +911,37 @@ class Client extends EventEmitter { */ async getProfilePicUrl(contactId) { const profilePic = await this.pupPage.evaluate((contactId) => { - return window.Store.Wap.profilePicFind(contactId); + const chatWid = window.Store.WidFactory.createWid(contactId); + return window.Store.getProfilePicFull(chatWid); }, contactId); return profilePic ? profilePic.eurl : undefined; } + /** + * Gets the Contact's common groups with you. Returns empty array if you don't have any common group. + * @param {string} contactId the whatsapp user's ID (_serialized format) + * @returns {Promise} + */ + async getCommonGroups(contactId) { + const commonGroups = await this.pupPage.evaluate(async (contactId) => { + const contact = window.Store.Contact.get(contactId); + if (contact.commonGroups) { + return contact.commonGroups.serialize(); + } + const status = await window.Store.findCommonGroups(contact); + if (status) { + return contact.commonGroups.serialize(); + } + return []; + }, contactId); + const chats = []; + for (const group of commonGroups) { + chats.push(group.id); + } + return chats; + } + /** * Force reset of connection state for the client */ @@ -868,10 +957,7 @@ class Client extends EventEmitter { * @returns {Promise} */ async isRegisteredUser(id) { - return await this.pupPage.evaluate(async (id) => { - let result = await window.Store.Wap.queryExist(id); - return result.jid !== undefined; - }, id); + return Boolean(await this.getNumberId(id)); } /** @@ -881,14 +967,15 @@ class Client extends EventEmitter { * @returns {Promise} */ async getNumberId(number) { - if (!number.endsWith('@c.us')) number += '@c.us'; - try { - return await this.pupPage.evaluate(async numberId => { - return window.WWebJS.getNumberId(numberId); - }, number); - } catch(_) { - return null; + if (!number.endsWith('@c.us')) { + number += '@c.us'; } + + return await this.pupPage.evaluate(async number => { + const result = await window.Store.QueryExist(number); + if (!result || result.wid === undefined) return null; + return result.wid; + }, number); } /** @@ -897,14 +984,14 @@ class Client extends EventEmitter { * @returns {Promise} */ async getFormattedNumber(number) { - if(!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net'); - if(!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`; - + if (!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net'); + if (!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`; + return await this.pupPage.evaluate(async numberId => { return window.Store.NumberInfo.formattedPhoneNumber(numberId); }, number); } - + /** * Get the country code of a WhatsApp ID. * @param {string} number Number or ID @@ -917,7 +1004,7 @@ class Client extends EventEmitter { return window.Store.NumberInfo.findCC(numberId); }, number); } - + /** * Create a new group * @param {string} name group title @@ -936,12 +1023,9 @@ class Client extends EventEmitter { } const createRes = await this.pupPage.evaluate(async (name, participantIds) => { - const res = await window.Store.Wap.createGroup(name, participantIds); - console.log(res); - if (!res.status === 200) { - throw 'An error occurred while creating the group!'; - } - + const participantWIDs = participantIds.map(p => window.Store.WidFactory.createWid(p)); + const id = window.Store.genId(); + const res = await window.Store.GroupUtils.sendCreateGroup(name, participantWIDs, undefined, id); return res; }, name, participants); @@ -962,9 +1046,9 @@ class Client extends EventEmitter { async getLabels() { const labels = await this.pupPage.evaluate(async () => { return window.WWebJS.getLabels(); - }); + }); - return labels.map(data => new Label(this , data)); + return labels.map(data => new Label(this, data)); } /** @@ -975,7 +1059,7 @@ class Client extends EventEmitter { async getLabelById(labelId) { const label = await this.pupPage.evaluate(async (labelId) => { return window.WWebJS.getLabel(labelId); - }, labelId); + }, labelId); return new Label(this, label); } @@ -985,12 +1069,12 @@ class Client extends EventEmitter { * @param {string} chatId * @returns {Promise>} */ - async getChatLabels(chatId){ + async getChatLabels(chatId) { const labels = await this.pupPage.evaluate(async (chatId) => { return window.WWebJS.getChatLabels(chatId); }, chatId); - return labels.map(data => new Label(this, data)); + return labels.map(data => new Label(this, data)); } /** @@ -998,16 +1082,16 @@ class Client extends EventEmitter { * @param {string} labelId * @returns {Promise>} */ - async getChatsByLabelId(labelId){ + async getChatsByLabelId(labelId) { const chatIds = await this.pupPage.evaluate(async (labelId) => { const label = window.Store.Label.get(labelId); const labelItems = label.labelItemCollection.models; return labelItems.reduce((result, item) => { - if(item.parentType === 'Chat'){ + if (item.parentType === 'Chat') { result.push(item.parentId); } return result; - },[]); + }, []); }, labelId); return Promise.all(chatIds.map(id => this.getChatById(id))); @@ -1020,7 +1104,7 @@ class Client extends EventEmitter { async getBlockedContacts() { const blockedContacts = await this.pupPage.evaluate(() => { let chatIds = window.Store.Blocklist.models.map(a => a.id._serialized); - return Promise.all(chatIds.map(id => window.WWebJS.getContact(id))); + return Promise.all(chatIds.map(id => window.WWebJS.getContact(id))); }); return blockedContacts.map(contact => ContactFactory.create(this.client, contact)); diff --git a/src/structures/ClientInfo.js b/src/structures/ClientInfo.js index 4dcd00e..b6cb2d6 100644 --- a/src/structures/ClientInfo.js +++ b/src/structures/ClientInfo.js @@ -20,12 +20,6 @@ class ClientInfo extends Base { */ this.pushname = data.pushname; - /** - * @type {object} - * @deprecated Use .wid instead - */ - this.me = data.wid; - /** * Current user ID * @type {object} @@ -33,18 +27,19 @@ class ClientInfo extends Base { this.wid = data.wid; /** - * Information about the phone this client is connected to + * Information about the phone this client is connected to. Not available in multi-device. * @type {object} * @property {string} wa_version WhatsApp Version running on the phone * @property {string} os_version OS Version running on the phone (iOS or Android version) * @property {string} device_manufacturer Device manufacturer * @property {string} device_model Device model * @property {string} os_build_number OS build number + * @deprecated */ this.phone = data.phone; /** - * Platform the phone is running on + * Platform WhatsApp is running on * @type {string} */ this.platform = data.platform; @@ -57,6 +52,7 @@ class ClientInfo extends Base { * @returns {object} batteryStatus * @returns {number} batteryStatus.battery - The current battery percentage * @returns {boolean} batteryStatus.plugged - Indicates if the phone is plugged in (true) or not (false) + * @deprecated */ async getBatteryStatus() { return await this.client.pupPage.evaluate(() => { @@ -64,7 +60,6 @@ class ClientInfo extends Base { return { battery, plugged }; }); } - } module.exports = ClientInfo; \ No newline at end of file diff --git a/src/structures/Contact.js b/src/structures/Contact.js index 8444840..7c20012 100644 --- a/src/structures/Contact.js +++ b/src/structures/Contact.js @@ -183,7 +183,8 @@ class Contact extends Base { */ async getAbout() { const about = await this.client.pupPage.evaluate(async (contactId) => { - return window.Store.Wap.statusFind(contactId); + const wid = window.Store.WidFactory.createWid(contactId); + return window.Store.StatusUtils.getStatus(wid); }, this.id._serialized); if (typeof about.status !== 'string') @@ -191,6 +192,14 @@ class Contact extends Base { return about.status; } + + /** + * Gets the Contact's common groups with you. Returns empty array if you don't have any common group. + * @returns {Promise} + */ + async getCommonGroups() { + return await this.client.getCommonGroups(this.id._serialized); + } } diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 76ff151..fc837a2 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -60,7 +60,9 @@ class GroupChat extends Chat { */ async addParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { - return window.Store.Wap.addParticipants(chatId, participantIds); + const chatWid = window.Store.WidFactory.createWid(chatId); + const participantWids = participantIds.map(p => window.Store.WidFactory.createWid(p)); + return window.Store.GroupParticipants.sendAddParticipants(chatWid, participantWids); }, this.id._serialized, participantIds); } @@ -71,7 +73,9 @@ class GroupChat extends Chat { */ async removeParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { - return window.Store.Wap.removeParticipants(chatId, participantIds); + const chatWid = window.Store.WidFactory.createWid(chatId); + const participantWids = participantIds.map(p => window.Store.WidFactory.createWid(p)); + return window.Store.GroupParticipants.sendRemoveParticipants(chatWid, participantWids); }, this.id._serialized, participantIds); } @@ -82,7 +86,9 @@ class GroupChat extends Chat { */ async promoteParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { - return window.Store.Wap.promoteParticipants(chatId, participantIds); + const chatWid = window.Store.WidFactory.createWid(chatId); + const participantWids = participantIds.map(p => window.Store.WidFactory.createWid(p)); + return window.Store.GroupParticipants.sendPromoteParticipants(chatWid, participantWids); }, this.id._serialized, participantIds); } @@ -93,39 +99,53 @@ class GroupChat extends Chat { */ async demoteParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { - return window.Store.Wap.demoteParticipants(chatId, participantIds); + const chatWid = window.Store.WidFactory.createWid(chatId); + const participantWids = participantIds.map(p => window.Store.WidFactory.createWid(p)); + return window.Store.GroupParticipants.sendDemoteParticipants(chatWid, participantWids); }, this.id._serialized, participantIds); } /** * Updates the group subject * @param {string} subject - * @returns {Promise} + * @returns {Promise} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions. */ async setSubject(subject) { - let res = await this.client.pupPage.evaluate((chatId, subject) => { - return window.Store.Wap.changeSubject(chatId, subject); + const success = await this.client.pupPage.evaluate(async (chatId, subject) => { + const chatWid = window.Store.WidFactory.createWid(chatId); + try { + return await window.Store.GroupUtils.sendSetGroupSubject(chatWid, subject); + } catch (err) { + if(err.name === 'ServerStatusCodeError') return false; + throw err; + } }, this.id._serialized, subject); - if(res.status == 200) { - this.name = subject; - } + if(!success) return false; + this.name = subject; + return true; } /** * Updates the group description * @param {string} description - * @returns {Promise} + * @returns {Promise} Returns true if the description was properly updated. This can return false if the user does not have the necessary permissions. */ async setDescription(description) { - let res = await this.client.pupPage.evaluate((chatId, description) => { - let descId = window.Store.GroupMetadata.get(chatId).descId; - return window.Store.Wap.setGroupDescription(chatId, description, window.Store.genId(), descId); + const success = await this.client.pupPage.evaluate(async (chatId, description) => { + const chatWid = window.Store.WidFactory.createWid(chatId); + let descId = window.Store.GroupMetadata.get(chatWid).descId; + try { + return await window.Store.GroupUtils.sendSetGroupDescription(chatWid, description, window.Store.genId(), descId); + } catch (err) { + if(err.name === 'ServerStatusCodeError') return false; + throw err; + } }, this.id._serialized, description); - if (res.status == 200) { - this.groupMetadata.desc = description; - } + if(!success) return false; + this.groupMetadata.desc = description; + return true; } /** @@ -134,12 +154,18 @@ class GroupChat extends Chat { * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions. */ async setMessagesAdminsOnly(adminsOnly=true) { - let res = await this.client.pupPage.evaluate((chatId, value) => { - return window.Store.Wap.setGroupProperty(chatId, 'announcement', value); + const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => { + const chatWid = window.Store.WidFactory.createWid(chatId); + try { + return await window.Store.GroupUtils.sendSetGroupProperty(chatWid, 'announcement', adminsOnly ? 1 : 0); + } catch (err) { + if(err.name === 'ServerStatusCodeError') return false; + throw err; + } }, this.id._serialized, adminsOnly); - if (res.status !== 200) return false; - + if(!success) return false; + this.groupMetadata.announce = adminsOnly; return true; } @@ -150,11 +176,17 @@ class GroupChat extends Chat { * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions. */ async setInfoAdminsOnly(adminsOnly=true) { - let res = await this.client.pupPage.evaluate((chatId, value) => { - return window.Store.Wap.setGroupProperty(chatId, 'restrict', value); + const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => { + const chatWid = window.Store.WidFactory.createWid(chatId); + try { + return await window.Store.GroupUtils.sendSetGroupProperty(chatWid, 'restrict', adminsOnly ? 1 : 0); + } catch (err) { + if(err.name === 'ServerStatusCodeError') return false; + throw err; + } }, this.id._serialized, adminsOnly); - if (res.status !== 200) return false; + if(!success) return false; this.groupMetadata.restrict = adminsOnly; return true; @@ -165,25 +197,25 @@ class GroupChat extends Chat { * @returns {Promise} Group's invite code */ async getInviteCode() { - let res = await this.client.pupPage.evaluate(chatId => { - return window.Store.Wap.groupInviteCode(chatId); + const code = await this.client.pupPage.evaluate(async chatId => { + const chatWid = window.Store.WidFactory.createWid(chatId); + return window.Store.Invite.sendQueryGroupInviteCode(chatWid); }, this.id._serialized); - if (res.status == 200) { - return res.code; - } - - throw new Error('Not authorized'); + return code; } /** * Invalidates the current group invite code and generates a new one - * @returns {Promise} + * @returns {Promise} New invite code */ async revokeInvite() { - return await this.client.pupPage.evaluate(chatId => { - return window.Store.Wap.revokeGroupInvite(chatId); + const code = await this.client.pupPage.evaluate(chatId => { + const chatWid = window.Store.WidFactory.createWid(chatId); + return window.Store.Invite.sendRevokeGroupInviteCode(chatWid); }, this.id._serialized); + + return code; } /** @@ -191,8 +223,9 @@ class GroupChat extends Chat { * @returns {Promise} */ async leave() { - return await this.client.pupPage.evaluate(chatId => { - return window.Store.Wap.leaveGroup(chatId); + await this.client.pupPage.evaluate(chatId => { + const chatWid = window.Store.WidFactory.createWid(chatId); + return window.Store.GroupUtils.sendExitGroup(chatWid); }, this.id._serialized); } diff --git a/src/structures/Message.js b/src/structures/Message.js index 5f6e4f0..6a326b9 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -19,13 +19,14 @@ class Message extends Base { } _patch(data) { + this._data = data; + /** * MediaKey that represents the sticker 'ID' * @type {string} */ this.mediaKey = data.mediaKey; - - + /** * ID that represents the message * @type {object} @@ -50,7 +51,7 @@ class Message extends Base { */ this.body = this.hasMedia ? data.caption || '' : data.body || ''; - /** + /** * Message type * @type {MessageTypes} */ @@ -70,9 +71,9 @@ class Message extends Base { /** * ID for who this message is for. - * + * * If the message is sent by the current user, it will be the Chat to which the message is being sent. - * If the message is sent by another user, it will be the ID for the current user. + * If the message is sent by another user, it will be the ID for the current user. * @type {string} */ this.to = (typeof (data.to) === 'object' && data.to !== null) ? data.to._serialized : data.to; @@ -87,8 +88,8 @@ class Message extends Base { * String that represents from which device type the message was sent * @type {string} */ - this.deviceType = data.id.id.length > 21 ? 'android' : data.id.id.substring(0,2) =='3A' ? 'ios' : 'web'; - + this.deviceType = data.id.id.length > 21 ? 'android' : data.id.id.substring(0, 2) == '3A' ? 'ios' : 'web'; + /** * Indicates if the message was forwarded * @type {boolean} @@ -114,14 +115,14 @@ class Message extends Base { * @type {boolean} */ this.isStarred = data.star; - + /** * Indicates if the message was a broadcast * @type {boolean} */ this.broadcast = data.broadcast; - /** + /** * Indicates if the message was sent by the current user * @type {boolean} */ @@ -157,7 +158,7 @@ class Message extends Base { fromId: data.from._serialized, toId: data.to._serialized } : undefined; - + /** * Indicates the mentions in the message body. * @type {Array} @@ -214,7 +215,7 @@ class Message extends Base { /** * Links included in the message. * @type {Array<{link: string, isSuspicious: boolean}>} - * + * */ this.links = data.links; @@ -222,7 +223,7 @@ class Message extends Base { if (data.dynamicReplyButtons) { this.dynamicReplyButtons = data.dynamicReplyButtons; } - + /** Selected Button Id **/ if (data.selectedButtonId) { this.selectedButtonId = data.selectedButtonId; @@ -232,7 +233,7 @@ class Message extends Base { if (data.listResponse && data.listResponse.singleSelectReply.selectedRowId) { this.selectedRowId = data.listResponse.singleSelectReply.selectedRowId; } - + return super._patch(data); } @@ -240,6 +241,32 @@ class Message extends Base { return this.fromMe ? this.to : this.from; } + /** + * Reloads this Message object's data in-place with the latest values from WhatsApp Web. + * Note that the Message must still be in the web app cache for this to work, otherwise will return null. + * @returns {Promise} + */ + async reload() { + const newData = await this.client.pupPage.evaluate((msgId) => { + const msg = window.Store.Msg.get(msgId); + if(!msg) return null; + return window.WWebJS.getMessageModel(msg); + }, this.id._serialized); + + if(!newData) return null; + + this._patch(newData); + return this; + } + + /** + * Returns message in a raw format + * @type {Object} + */ + get rawData() { + return this._data; + } + /** * Returns the Chat this message was sent in * @returns {Promise} @@ -280,12 +307,12 @@ class Message extends Base { } /** - * Sends a message as a reply to this message. If chatId is specified, it will be sent - * through the specified Chat. If not, it will send the message + * Sends a message as a reply to this message. If chatId is specified, it will be sent + * through the specified Chat. If not, it will send the message * in the same Chat as the original message was sent. - * - * @param {string|MessageMedia|Location} content - * @param {string} [chatId] + * + * @param {string|MessageMedia|Location} content + * @param {string} [chatId] * @param {MessageSendOptions} [options] * @returns {Promise} */ @@ -309,10 +336,10 @@ class Message extends Base { async acceptGroupV4Invite() { return await this.client.acceptGroupV4Invite(this.inviteV4); } - + /** * Forwards this message to another chat - * + * * @param {string|Chat} chat Chat model or chat ID to which the message will be forwarded * @returns {Promise} */ @@ -342,7 +369,7 @@ class Message extends Base { if (msg.mediaData.mediaStage != 'RESOLVED') { // try to resolve media await msg.downloadMedia({ - downloadEvenIfExpensive: true, + downloadEvenIfExpensive: true, rmrReason: 1 }); } @@ -362,9 +389,9 @@ class Message extends Base { type: msg.type, signal: (new AbortController).signal }); - + const data = window.WWebJS.arrayBufferToBase64(decryptedMedia); - + return { data, mimetype: msg.mimetype, @@ -440,14 +467,10 @@ class Message extends Base { async getInfo() { const info = await this.client.pupPage.evaluate(async (msgId) => { const msg = window.Store.Msg.get(msgId); - if(!msg) return null; - - return await window.Store.Wap.queryMsgInfo(msg.id); - }, this.id._serialized); + if (!msg) return null; - if(info.status) { - return null; - } + return await window.Store.MessageInfo.sendQueryMsgInfo(msg.id); + }, this.id._serialized); return info; } diff --git a/src/util/Constants.js b/src/util/Constants.js index b95ccd1..1b27ca5 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -7,10 +7,9 @@ exports.DefaultOptions = { headless: true, defaultViewport: null }, - session: false, - qrTimeoutMs: 45000, - qrRefreshIntervalMs: 20000, - authTimeoutMs: 45000, + dataPath: './WWebJS/', + useDeprecatedSessionAuth: false, + authTimeoutMs: 0, qrMaxRetries: 0, takeoverOnConflict: false, takeoverTimeoutMs: 0, diff --git a/src/util/Injected.js b/src/util/Injected.js index 6031752..0169494 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -8,36 +8,59 @@ exports.ExposeStore = (moduleRaidStr) => { window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); window.Store.AppState = window.mR.findModule('Socket')[0].Socket; window.Store.Conn = window.mR.findModule('Conn')[0].Conn; - window.Store.Wap = window.mR.findModule('queryLinkPreview')[0].default; - window.Store.SendSeen = window.mR.findModule('sendSeen')[0]; - window.Store.SendClear = window.mR.findModule('sendClear')[0]; - window.Store.SendDelete = window.mR.findModule('sendDelete')[0]; - window.Store.genId = window.mR.findModule('randomId')[0].randomId; - window.Store.SendMessage = window.mR.findModule('addAndSendMsgToChat')[0]; - window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default; + window.Store.BlockContact = window.mR.findModule('blockContact')[0]; + window.Store.Call = window.mR.findModule('CallCollection')[0].CallCollection; + window.Store.Cmd = window.mR.findModule('Cmd')[0].Cmd; + window.Store.CryptoLib = window.mR.findModule('decryptE2EMedia')[0]; + window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager; + window.Store.Features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0].GK; + window.Store.genId = window.mR.findModule('newTag')[0].newTag; + window.Store.GroupMetadata = window.mR.findModule((module) => module.default && module.default.handlePendingInvite)[0].default; window.Store.Invite = window.mR.findModule('sendJoinGroupViaInvite')[0]; - window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default; + window.Store.InviteInfo = window.mR.findModule('sendQueryGroupInvite')[0]; + window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; window.Store.MediaPrep = window.mR.findModule('MediaPrep')[0]; window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0]; - window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0]; window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0]; - window.Store.Cmd = window.mR.findModule('Cmd')[0].Cmd; window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0]; - window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; + window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0]; + window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default; + window.Store.MessageInfo = window.mR.findModule('sendQueryMsgInfo')[0]; + window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default; + window.Store.QueryExist = window.mR.findModule(module => typeof module.default === 'function' && module.default.toString().includes('Should not reach queryExists MD'))[0].default; + window.Store.QueryProduct = window.mR.findModule('queryProduct')[0]; + window.Store.QueryOrder = window.mR.findModule('queryOrder')[0]; + window.Store.SendClear = window.mR.findModule('sendClear')[0]; + window.Store.SendDelete = window.mR.findModule('sendDelete')[0]; + window.Store.SendMessage = window.mR.findModule('addAndSendMsgToChat')[0]; + window.Store.SendSeen = window.mR.findModule('sendSeen')[0]; + window.Store.Sticker = window.mR.findModule('Sticker')[0].Sticker; + window.Store.User = window.mR.findModule('getMaybeMeUser')[0]; + window.Store.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default; window.Store.Validators = window.mR.findModule('findLinks')[0]; + window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; + window.Store.Wap = window.mR.findModule('queryLinkPreview')[0].default; window.Store.WidFactory = window.mR.findModule('createWid')[0]; - window.Store.BlockContact = window.mR.findModule('blockContact')[0]; - window.Store.GroupMetadata = window.mR.findModule((module) => module.default && module.default.handlePendingInvite)[0].default; - window.Store.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; - window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; - window.Store.Features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0].GK; - window.Store.QueryOrder = window.mR.findModule('queryOrder')[0]; - window.Store.QueryProduct = window.mR.findModule('queryProduct')[0]; - window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager; - window.Store.Call = window.mR.findModule('CallCollection')[0].CallCollection; + window.Store.getProfilePicFull = window.mR.findModule('getProfilePicFull')[0].getProfilePicFull; + window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0]; + window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0]; + window.Store.GroupParticipants = window.mR.findModule('sendPromoteParticipants')[0]; + window.Store.JoinInviteV4 = window.mR.findModule('sendJoinGroupViaInviteV4')[0]; + window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups; + window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0]; + window.Store.StickerTools = { + ...window.mR.findModule('toWebpSticker')[0], + ...window.mR.findModule('addWebpMetadata')[0] + }; + + window.Store.GroupUtils = { + ...window.mR.findModule('sendCreateGroup')[0], + ...window.mR.findModule('sendSetGroupSubject')[0], + ...window.mR.findModule('markExited')[0] + }; - if(!window.Store.Chat._find) { + if (!window.Store.Chat._find) { window.Store.Chat._find = e => { const target = window.Store.Chat.get(e); return target ? Promise.resolve(target) : Promise.resolve({ @@ -50,14 +73,6 @@ exports.ExposeStore = (moduleRaidStr) => { exports.LoadUtils = () => { window.WWebJS = {}; - window.WWebJS.getNumberId = async (id) => { - - let result = await window.Store.Wap.queryExist(id); - if (result.jid === undefined) - throw 'The number provided is not a registered whatsapp user'; - return result.jid; - }; - window.WWebJS.sendSeen = async (chatId) => { let chat = window.Store.Chat.get(chatId); if (chat !== undefined) { @@ -67,14 +82,14 @@ exports.LoadUtils = () => { return false; }; - + window.WWebJS.sendMessage = async (chat, content, options = {}) => { let attOptions = {}; if (options.attachment) { - attOptions = options.sendMediaAsSticker + attOptions = options.sendMediaAsSticker ? await window.WWebJS.processStickerData(options.attachment) : await window.WWebJS.processMediaData(options.attachment, { - forceVoice: options.sendAudioAsVoice, + forceVoice: options.sendAudioAsVoice, forceDocument: options.sendMediaAsDocument, forceGif: options.sendVideoAsGif }); @@ -144,22 +159,26 @@ exports.LoadUtils = () => { if (options.linkPreview) { delete options.linkPreview; - const link = window.Store.Validators.findLink(content); - if (link) { - const preview = await window.Store.Wap.queryLinkPreview(link.url); - preview.preview = true; - preview.subtype = 'url'; - options = { ...options, ...preview }; + + // Not supported yet by WhatsApp Web on MD + if(!window.Store.Features.features.MD_BACKEND) { + const link = window.Store.Validators.findLink(content); + if (link) { + const preview = await window.Store.Wap.queryLinkPreview(link.url); + preview.preview = true; + preview.subtype = 'url'; + options = { ...options, ...preview }; + } } } let buttonOptions = {}; if(options.buttons){ let caption; - if(options.buttons.type === 'chat') { + if (options.buttons.type === 'chat') { content = options.buttons.body; caption = content; - }else{ + } else { caption = options.caption ? options.caption : ' '; //Caption can't be empty } buttonOptions = { @@ -192,11 +211,16 @@ exports.LoadUtils = () => { delete options.list; delete listOptions.list.footer; } - + + const meUser = window.Store.User.getMaybeMeUser(); + const isMD = window.Store.Features.features.MD_BACKEND; + const newMsgId = new window.Store.MsgKey({ - fromMe: true, - remote: chat.id, + from: meUser, + to: chat.id, id: window.Store.genId(), + participant: isMD && chat.id.isGroup() ? meUser : undefined, + selfDir: 'out', }); const extraOptions = options.extraOptions || {}; @@ -213,7 +237,7 @@ exports.LoadUtils = () => { id: newMsgId, ack: 0, body: content, - from: window.Store.Conn.wid, + from: meUser, to: chat.id, local: true, self: 'out', @@ -234,11 +258,25 @@ exports.LoadUtils = () => { return window.Store.Msg.get(newMsgId._serialized); }; + window.WWebJS.toStickerData = async (mediaInfo) => { + if (mediaInfo.mimetype == 'image/webp') return mediaInfo; + + const file = window.WWebJS.mediaInfoToFile(mediaInfo); + const webpSticker = await window.Store.StickerTools.toWebpSticker(file); + const webpBuffer = await webpSticker.arrayBuffer(); + const data = window.WWebJS.arrayBufferToBase64(webpBuffer); + + return { + mimetype: 'image/webp', + data + }; + }; + window.WWebJS.processStickerData = async (mediaInfo) => { if (mediaInfo.mimetype !== 'image/webp') throw new Error('Invalid media type'); const file = window.WWebJS.mediaInfoToFile(mediaInfo); - let filehash = await window.WWebJS.getFileHash(file); + let filehash = await window.WWebJS.getFileHash(file); let mediaKey = await window.WWebJS.generateHash(32); const controller = new AbortController(); @@ -324,11 +362,11 @@ exports.LoadUtils = () => { window.WWebJS.getMessageModel = message => { const msg = message.serialize(); - + msg.isEphemeral = message.isEphemeral; msg.isStatusV3 = message.isStatusV3; - msg.links = (message.getLinks()).map(link => ({ - link: link.href, + msg.links = (message.getLinks()).map(link => ({ + link: link.href, isSuspicious: Boolean(link.suspiciousCharacters && link.suspiciousCharacters.size) })); @@ -338,28 +376,30 @@ exports.LoadUtils = () => { if (msg.dynamicReplyButtons) { msg.dynamicReplyButtons = JSON.parse(JSON.stringify(msg.dynamicReplyButtons)); } - if(msg.replyButtons) { + if (msg.replyButtons) { msg.replyButtons = JSON.parse(JSON.stringify(msg.replyButtons)); } - if(typeof msg.id.remote === 'object') { - msg.id = Object.assign({}, msg.id, {remote: msg.id.remote._serialized}); + if (typeof msg.id.remote === 'object') { + msg.id = Object.assign({}, msg.id, { remote: msg.id.remote._serialized }); } - + delete msg.pendingAckUpdate; - + return msg; }; window.WWebJS.getChatModel = async chat => { + let res = chat.serialize(); res.isGroup = chat.isGroup; res.formattedTitle = chat.formattedTitle; res.isMuted = chat.mute && chat.mute.isMuted; if (chat.groupMetadata) { - await window.Store.GroupMetadata.update(chat.id._serialized); + const chatWid = window.Store.WidFactory.createWid((chat.id._serialized)); + await window.Store.GroupMetadata.update(chatWid); res.groupMetadata = chat.groupMetadata.serialize(); } @@ -414,7 +454,7 @@ exports.LoadUtils = () => { }; window.WWebJS.mediaInfoToFile = ({ data, mimetype, filename }) => { - const binaryData = atob(data); + const binaryData = window.atob(data); const buffer = new ArrayBuffer(binaryData.length); const view = new Uint8Array(buffer); @@ -431,15 +471,15 @@ exports.LoadUtils = () => { window.WWebJS.arrayBufferToBase64 = (arrayBuffer) => { let binary = ''; - const bytes = new Uint8Array( arrayBuffer ); + const bytes = new Uint8Array(arrayBuffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { - binary += String.fromCharCode( bytes[ i ] ); + binary += String.fromCharCode(bytes[i]); } - return window.btoa( binary ); + return window.btoa(binary); }; - window.WWebJS.getFileHash = async (data) => { + window.WWebJS.getFileHash = async (data) => { let buffer = await data.arrayBuffer(); const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); return btoa(String.fromCharCode(...new Uint8Array(hashBuffer))); @@ -449,7 +489,7 @@ exports.LoadUtils = () => { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; - for ( var i = 0; i < length; i++ ) { + for (var i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; @@ -474,15 +514,18 @@ exports.LoadUtils = () => { }; window.WWebJS.sendChatstate = async (state, chatId) => { + if (window.Store.Features.features.MD_BACKEND) { + chatId = window.Store.WidFactory.createWid(chatId); + } switch (state) { case 'typing': - await window.Store.Wap.sendChatstateComposing(chatId); + await window.Store.ChatState.sendChatStateComposing(chatId); break; case 'recording': - await window.Store.Wap.sendChatstateRecording(chatId); + await window.Store.ChatState.sendChatStateRecording(chatId); break; case 'stop': - await window.Store.Wap.sendChatstatePaused(chatId); + await window.Store.ChatState.sendChatStatePaused(chatId); break; default: throw 'Invalid chatstate'; @@ -494,7 +537,7 @@ exports.LoadUtils = () => { window.WWebJS.getLabelModel = label => { let res = label.serialize(); res.hexColor = label.hexColor; - + return res; }; @@ -527,20 +570,3 @@ exports.LoadUtils = () => { return undefined; }; }; - -exports.MarkAllRead = () => { - let Chats = window.Store.Chat.models; - - for (let chatIndex in Chats) { - if (isNaN(chatIndex)) { - continue; - } - - let chat = Chats[chatIndex]; - - if (chat.unreadCount > 0) { - chat.markSeen(); - window.Store.Wap.sendConversationSeen(chat.id, chat.getLastMsgKeyForAction(), chat.unreadCount - chat.pendingSeenCount); - } - } -}; diff --git a/src/util/Util.js b/src/util/Util.js index 4bfc378..a3a6bb9 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -1,6 +1,5 @@ 'use strict'; -const sharp = require('sharp'); const path = require('path'); const Crypto = require('crypto'); const { tmpdir } = require('os'); @@ -14,7 +13,6 @@ const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); * Utility methods */ class Util { - constructor() { throw new Error(`The ${this.constructor.name} class may not be instantiated.`); } @@ -23,7 +21,7 @@ class Util { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; - for ( var i = 0; i < length; i++ ) { + for (var i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; @@ -55,33 +53,19 @@ class Util { * * @returns {Promise} media in webp format */ - static async formatImageToWebpSticker(media) { + static async formatImageToWebpSticker(media, pupPage) { if (!media.mimetype.includes('image')) throw new Error('media is not a image'); - + if (media.mimetype.includes('webp')) { return media; } - - const buff = Buffer.from(media.data, 'base64'); - - let sharpImg = sharp(buff); - sharpImg = sharpImg.webp(); - - sharpImg = sharpImg.resize(512, 512, { - fit: 'contain', - background: { r: 0, g: 0, b: 0, alpha: 0 }, - }); - - let webpBase64 = (await sharpImg.toBuffer()).toString('base64'); - - return { - mimetype: 'image/webp', - data: webpBase64, - filename: media.filename, - }; + + return pupPage.evaluate((media) => { + return window.WWebJS.toStickerData(media); + }, media); } - + /** * Formats a video to webp * @param {MessageMedia} media @@ -91,14 +75,14 @@ class Util { static async formatVideoToWebpSticker(media) { if (!media.mimetype.includes('video')) throw new Error('media is not a video'); - + const videoType = media.mimetype.split('/')[1]; const tempFile = path.join( tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp` ); - + const stream = new (require('stream').Readable)(); const buffer = Buffer.from( media.data.replace(`data:${media.mimetype};base64,`, ''), @@ -135,17 +119,17 @@ class Util { .toFormat('webp') .save(tempFile); }); - + const data = await fs.readFile(tempFile, 'base64'); await fs.unlink(tempFile); - - return { + + return { mimetype: 'image/webp', data: data, filename: media.filename, }; } - + /** * Sticker metadata. * @typedef {Object} StickerMetadata @@ -161,14 +145,14 @@ class Util { * * @returns {Promise} media in webp format */ - static async formatToWebpSticker(media, metadata) { + static async formatToWebpSticker(media, metadata, pupPage) { let webpMedia; - if (media.mimetype.includes('image')) - webpMedia = await this.formatImageToWebpSticker(media); - else if (media.mimetype.includes('video')) + if (media.mimetype.includes('image')) + webpMedia = await this.formatImageToWebpSticker(media, pupPage); + else if (media.mimetype.includes('video')) webpMedia = await this.formatVideoToWebpSticker(media); - else + else throw new Error('Invalid media format'); if (metadata.name || metadata.author) { diff --git a/tests/README.md b/tests/README.md index e1c9544..4b98aa8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -3,8 +3,12 @@ These tests require an authenticated WhatsApp Web session, as well as an additional phone that you can send messages to. This can be configured using the following environment variables: -- `WWEBJS_TEST_SESSION`: A JSON-formatted string with the session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. -- `WWEBJS_TEST_SESSION_PATH`: Path to a JSON file that contains the session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. +- `WWEBJS_TEST_SESSION`: A JSON-formatted string with legacy auth session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. +- `WWEBJS_TEST_SESSION_PATH`: Path to a JSON file that contains the legacy auth session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. +- `WWEBJS_TEST_CLIENT_ID`: `clientId` to use for local file based authentication. - `WWEBJS_TEST_REMOTE_ID`: A valid WhatsApp ID that you can send messages to, e.g. `123456789@c.us`. It should be different from the ID used by the provided session. -You *must* set `WWEBJS_TEST_REMOTE_ID` **and** either `WWEBJS_TEST_SESSION` or `WWEBJS_TEST_SESSION_PATH` for the tests to run properly. +You *must* set `WWEBJS_TEST_REMOTE_ID` **and** either `WWEBJS_TEST_SESSION`, `WWEBJS_TEST_SESSION_PATH` or `WWEBJS_TEST_CLIENT_ID` for the tests to run properly. + +### Multidevice +Some of the tested functionality depends on whether the account has multidevice enabled or not. If you are using multidevice, you should set `WWEBJS_TEST_MD=1`. \ No newline at end of file diff --git a/tests/client.js b/tests/client.js index 49c69ec..c99d672 100644 --- a/tests/client.js +++ b/tests/client.js @@ -1,4 +1,5 @@ -const {expect} = require('chai'); +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); const sinon = require('sinon'); const helper = require('./helper'); @@ -9,7 +10,11 @@ const MessageMedia = require('../src/structures/MessageMedia'); const Location = require('../src/structures/Location'); const { MessageTypes, WAState } = require('../src/util/Constants'); +const expect = chai.expect; +chai.use(chaiAsPromised); + const remoteId = helper.remoteId; +const isMD = helper.isMD(); describe('Client', function() { describe('Authentication', function() { @@ -47,76 +52,6 @@ describe('Client', function() { expect(disconnectedCallback.calledOnceWith('Max qrcode retries reached')).to.eql(true); }); - it('should fail auth if session is invalid', async function() { - this.timeout(40000); - - const authFailCallback = sinon.spy(); - const qrCallback = sinon.spy(); - const readyCallback = sinon.spy(); - - const client = helper.createClient({ - options: { - session: { - WABrowserId: 'invalid', - WASecretBundle: 'invalid', - WAToken1: 'invalid', - WAToken2: 'invalid' - }, - authTimeoutMs: 10000, - restartOnAuthFail: false - } - }); - - client.on('qr', qrCallback); - client.on('auth_failure', authFailCallback); - client.on('ready', readyCallback); - - client.initialize(); - - await helper.sleep(25000); - - expect(authFailCallback.called).to.equal(true); - expect(authFailCallback.args[0][0]).to.equal('Unable to log in. Are the session details valid?'); - - expect(readyCallback.called).to.equal(false); - expect(qrCallback.called).to.equal(false); - - await client.destroy(); - }); - - it('can restart without a session if session was invalid and restartOnAuthFail=true', async function() { - this.timeout(40000); - - const authFailCallback = sinon.spy(); - const qrCallback = sinon.spy(); - - const client = helper.createClient({ - options:{ - session: { - WABrowserId: 'invalid', - WASecretBundle: 'invalid', - WAToken1: 'invalid', - WAToken2: 'invalid' - }, - authTimeoutMs: 10000, - restartOnAuthFail: true - } - }); - - client.on('auth_failure', authFailCallback); - client.on('qr', qrCallback); - - client.initialize(); - - await helper.sleep(35000); - - expect(authFailCallback.called).to.equal(true); - expect(qrCallback.called).to.equal(true); - expect(qrCallback.args[0][0]).to.have.lengthOf(152); - - await client.destroy(); - }); - it('should authenticate with existing session', async function() { this.timeout(40000); @@ -124,7 +59,10 @@ describe('Client', function() { const qrCallback = sinon.spy(); const readyCallback = sinon.spy(); - const client = helper.createClient({withSession: true}); + const client = helper.createClient({ + authenticated: true, + }); + client.on('qr', qrCallback); client.on('authenticated', authenticatedCallback); client.on('ready', readyCallback); @@ -132,59 +70,137 @@ describe('Client', function() { await client.initialize(); expect(authenticatedCallback.called).to.equal(true); - const newSession = authenticatedCallback.args[0][0]; - expect(newSession).to.have.key([ - 'WABrowserId', - 'WASecretBundle', - 'WAToken1', - 'WAToken2' - ]); - expect(authenticatedCallback.called).to.equal(true); + + if(helper.isUsingDeprecatedSession()) { + const newSession = authenticatedCallback.args[0][0]; + expect(newSession).to.have.key([ + 'WABrowserId', + 'WASecretBundle', + 'WAToken1', + 'WAToken2' + ]); + } + expect(readyCallback.called).to.equal(true); expect(qrCallback.called).to.equal(false); await client.destroy(); - }); - - it('can take over if client was logged in somewhere else with takeoverOnConflict=true', async function() { - this.timeout(40000); - - const readyCallback1 = sinon.spy(); - const readyCallback2 = sinon.spy(); - const disconnectedCallback1 = sinon.spy(); - const disconnectedCallback2 = sinon.spy(); - - const client1 = helper.createClient({ - withSession: true, - options: { takeoverOnConflict: true, takeoverTimeoutMs: 5000 } - }); - const client2 = helper.createClient({withSession: true}); - - client1.on('ready', readyCallback1); - client2.on('ready', readyCallback2); - client1.on('disconnected', disconnectedCallback1); - client2.on('disconnected', disconnectedCallback2); - - await client1.initialize(); - expect(readyCallback1.called).to.equal(true); - expect(readyCallback2.called).to.equal(false); - expect(disconnectedCallback1.called).to.equal(false); - expect(disconnectedCallback2.called).to.equal(false); - - await client2.initialize(); - expect(readyCallback2.called).to.equal(true); - expect(disconnectedCallback1.called).to.equal(false); - expect(disconnectedCallback2.called).to.equal(false); - - // wait for takeoverTimeoutMs to kick in - await helper.sleep(5200); - expect(disconnectedCallback1.called).to.equal(false); - expect(disconnectedCallback2.called).to.equal(true); - expect(disconnectedCallback2.calledWith(WAState.CONFLICT)).to.equal(true); - - await client1.destroy(); - }); + + describe('Non-MD only', function () { + if(!isMD) { + it('can take over if client was logged in somewhere else with takeoverOnConflict=true', async function() { + this.timeout(40000); + + const readyCallback1 = sinon.spy(); + const readyCallback2 = sinon.spy(); + const disconnectedCallback1 = sinon.spy(); + const disconnectedCallback2 = sinon.spy(); + + const client1 = helper.createClient({ + authenticated: true, + options: { takeoverOnConflict: true, takeoverTimeoutMs: 5000 } + }); + const client2 = helper.createClient({authenticated: true}); + + client1.on('ready', readyCallback1); + client2.on('ready', readyCallback2); + client1.on('disconnected', disconnectedCallback1); + client2.on('disconnected', disconnectedCallback2); + + await client1.initialize(); + expect(readyCallback1.called).to.equal(true); + expect(readyCallback2.called).to.equal(false); + expect(disconnectedCallback1.called).to.equal(false); + expect(disconnectedCallback2.called).to.equal(false); + + await client2.initialize(); + expect(readyCallback2.called).to.equal(true); + expect(disconnectedCallback1.called).to.equal(false); + expect(disconnectedCallback2.called).to.equal(false); + + // wait for takeoverTimeoutMs to kick in + await helper.sleep(5200); + expect(disconnectedCallback1.called).to.equal(false); + expect(disconnectedCallback2.called).to.equal(true); + expect(disconnectedCallback2.calledWith(WAState.CONFLICT)).to.equal(true); + + await client1.destroy(); + }); + + it('should fail auth if session is invalid', async function() { + this.timeout(40000); + + const authFailCallback = sinon.spy(); + const qrCallback = sinon.spy(); + const readyCallback = sinon.spy(); + + const client = helper.createClient({ + options: { + session: { + WABrowserId: 'invalid', + WASecretBundle: 'invalid', + WAToken1: 'invalid', + WAToken2: 'invalid' + }, + authTimeoutMs: 10000, + restartOnAuthFail: false, + useDeprecatedSessionAuth: true + } + }); + + client.on('qr', qrCallback); + client.on('auth_failure', authFailCallback); + client.on('ready', readyCallback); + + client.initialize(); + + await helper.sleep(25000); + + expect(authFailCallback.called).to.equal(true); + expect(authFailCallback.args[0][0]).to.equal('Unable to log in. Are the session details valid?'); + + expect(readyCallback.called).to.equal(false); + expect(qrCallback.called).to.equal(false); + + await client.destroy(); + }); + + it('can restart without a session if session was invalid and restartOnAuthFail=true', async function() { + this.timeout(40000); + + const authFailCallback = sinon.spy(); + const qrCallback = sinon.spy(); + + const client = helper.createClient({ + options:{ + session: { + WABrowserId: 'invalid', + WASecretBundle: 'invalid', + WAToken1: 'invalid', + WAToken2: 'invalid' + }, + authTimeoutMs: 10000, + restartOnAuthFail: true, + useDeprecatedSessionAuth: true + } + }); + + client.on('auth_failure', authFailCallback); + client.on('qr', qrCallback); + + client.initialize(); + + await helper.sleep(35000); + + expect(authFailCallback.called).to.equal(true); + expect(qrCallback.called).to.equal(true); + expect(qrCallback.args[0][0]).to.have.lengthOf(152); + + await client.destroy(); + }); + } + }); }); describe('Authenticated', function() { @@ -192,7 +208,7 @@ describe('Client', function() { before(async function() { this.timeout(35000); - client = helper.createClient({withSession: true}); + client = helper.createClient({authenticated: true}); await client.initialize(); }); @@ -221,27 +237,38 @@ describe('Client', function() { 'BlockContact', 'Call', 'Chat', + 'ChatState', 'Cmd', 'Conn', 'Contact', 'DownloadManager', 'Features', 'GroupMetadata', + 'GroupParticipants', + 'GroupUtils', 'Invite', + 'InviteInfo', + 'JoinInviteV4', 'Label', 'MediaObject', 'MediaPrep', 'MediaTypes', 'MediaUpload', + 'MessageInfo', 'Msg', 'MsgKey', 'OpaqueData', 'QueryOrder', 'QueryProduct', + 'PresenceUtils', + 'QueryExist', + 'QueryProduct', + 'QueryOrder', 'SendClear', 'SendDelete', 'SendMessage', 'SendSeen', + 'StatusUtils', 'Sticker', 'UploadUtils', 'UserConstructor', @@ -249,7 +276,9 @@ describe('Client', function() { 'Validators', 'Wap', 'WidFactory', - 'genId' + 'findCommonGroups', + 'genId', + 'getProfilePicFull', ]; const loadedModules = await client.pupPage.evaluate((expectedModules) => { @@ -535,8 +564,6 @@ END:VCARD`; describe('Search messages', function () { it('can search for messages', async function () { - this.timeout(5000); - const m1 = await client.sendMessage(remoteId, 'I\'m searching for Super Mario Brothers'); const m2 = await client.sendMessage(remoteId, 'This also contains Mario'); const m3 = await client.sendMessage(remoteId, 'Nothing of interest here, just Luigi'); @@ -581,4 +608,4 @@ END:VCARD`; }); }); }); -}); \ No newline at end of file +}); diff --git a/tests/helper.js b/tests/helper.js index febda54..bcd22a3 100644 --- a/tests/helper.js +++ b/tests/helper.js @@ -1,13 +1,25 @@ const path = require('path'); +const crypto = require('crypto'); const Client = require('../src/Client'); -const Util = require('../src/util/Util'); require('dotenv').config(); const remoteId = process.env.WWEBJS_TEST_REMOTE_ID; if(!remoteId) throw new Error('The WWEBJS_TEST_REMOTE_ID environment variable has not been set.'); +function isUsingDeprecatedSession() { + return Boolean(process.env.WWEBJS_TEST_SESSION || process.env.WWEBJS_TEST_SESSION_PATH); +} + +function isMD() { + return Boolean(process.env.WWEBJS_TEST_MD); +} + +if(isUsingDeprecatedSession() && isMD()) throw 'Cannot use deprecated sessions with WWEBJS_TEST_MD=true'; + function getSessionFromEnv() { + if (!isUsingDeprecatedSession()) return null; + const envSession = process.env.WWEBJS_TEST_SESSION; if(envSession) return JSON.parse(envSession); @@ -16,17 +28,27 @@ function getSessionFromEnv() { const absPath = path.resolve(process.cwd(), envSessionPath); return require(absPath); } - - throw new Error('No session found in environment.'); } -function createClient({withSession, options: additionalOpts}={}) { +function createClient({authenticated, options: additionalOpts}={}) { const options = {}; - if(withSession) { - options.session = getSessionFromEnv(); + + if(authenticated) { + const deprecatedSession = getSessionFromEnv(); + if(deprecatedSession) { + options.session = deprecatedSession; + options.useDeprecatedSessionAuth = true; + } else { + const clientId = process.env.WWEBJS_TEST_CLIENT_ID; + if(!clientId) throw new Error('No session found in environment.'); + options.clientId = clientId; + } + } else { + options.clientId = crypto.randomBytes(5).toString('hex'); } - return new Client(Util.mergeDefault(options, additionalOpts || {})); + const allOpts = {...options, ...(additionalOpts || {})}; + return new Client(allOpts); } function sleep(ms) { @@ -36,5 +58,7 @@ function sleep(ms) { module.exports = { sleep, createClient, - remoteId + isUsingDeprecatedSession, + isMD, + remoteId, }; \ No newline at end of file diff --git a/tests/structures/chat.js b/tests/structures/chat.js index 144aa5d..6148485 100644 --- a/tests/structures/chat.js +++ b/tests/structures/chat.js @@ -13,7 +13,7 @@ describe('Chat', function () { before(async function() { this.timeout(35000); - client = helper.createClient({ withSession: true }); + client = helper.createClient({ authenticated: true }); await client.initialize(); chat = await client.getChatById(remoteId); }); @@ -32,9 +32,9 @@ describe('Chat', function () { }); it('can fetch messages sent in a chat', async function () { - this.timeout(5000); await helper.sleep(1000); const msg = await chat.sendMessage('another message'); + await helper.sleep(500); const messages = await chat.fetchMessages(); expect(messages.length).to.be.greaterThanOrEqual(2); @@ -49,6 +49,7 @@ describe('Chat', function () { it('can use a limit when fetching messages sent in a chat', async function () { await helper.sleep(1000); const msg = await chat.sendMessage('yet another message'); + await helper.sleep(500); const messages = await chat.fetchMessages({limit: 1}); expect(messages).to.have.lengthOf(1); @@ -80,6 +81,8 @@ describe('Chat', function () { const res = await chat.sendSeen(); expect(res).to.equal(true); + await helper.sleep(1000); + // refresh chat chat = await client.getChatById(remoteId); expect(chat.unreadCount).to.equal(0); @@ -137,6 +140,8 @@ describe('Chat', function () { it('can mute a chat forever', async function() { await chat.mute(); + await helper.sleep(1000); + // refresh chat chat = await client.getChatById(remoteId); expect(chat.isMuted).to.equal(true); @@ -147,6 +152,8 @@ describe('Chat', function () { const unmuteDate = new Date(new Date().getTime() + (1000*60*60)); await chat.mute(unmuteDate); + await helper.sleep(1000); + // refresh chat chat = await client.getChatById(remoteId); expect(chat.isMuted).to.equal(true); @@ -168,9 +175,7 @@ describe('Chat', function () { // eslint-disable-next-line mocha/no-skipped-tests describe.skip('Destructive operations', function () { - it('can clear all messages from chat', async function () { - this.timeout(5000); - + it('can clear all messages from chat', async function () { const res = await chat.clearMessages(); expect(res).to.equal(true); diff --git a/tests/structures/group.js b/tests/structures/group.js new file mode 100644 index 0000000..6c47dd5 --- /dev/null +++ b/tests/structures/group.js @@ -0,0 +1,227 @@ +const { expect } = require('chai'); +const helper = require('../helper'); + +const remoteId = helper.remoteId; + +describe('Group', function() { + let client; + let group; + + before(async function() { + this.timeout(35000); + client = helper.createClient({ + authenticated: true, + }); + await client.initialize(); + + const createRes = await client.createGroup('My Awesome Group', [remoteId]); + expect(createRes.gid).to.exist; + await helper.sleep(500); + group = await client.getChatById(createRes.gid._serialized); + expect(group).to.exist; + }); + + beforeEach(async function () { + await helper.sleep(500); + }); + + describe('Settings', function () { + it('can change the group subject', async function () { + expect(group.name).to.equal('My Awesome Group'); + const res = await group.setSubject('My Amazing Group'); + expect(res).to.equal(true); + + await helper.sleep(1000); + + // reload + group = await client.getChatById(group.id._serialized); + expect(group.name).to.equal('My Amazing Group'); + }); + + it('can change the group description', async function () { + expect(group.description).to.equal(undefined); + const res = await group.setDescription('some description'); + expect(res).to.equal(true); + expect(group.description).to.equal('some description'); + + await helper.sleep(1000); + + // reload + group = await client.getChatById(group.id._serialized); + expect(group.description).to.equal('some description'); + }); + + it('can set only admins able to send messages', async function () { + expect(group.groupMetadata.announce).to.equal(false); + const res = await group.setMessagesAdminsOnly(); + expect(res).to.equal(true); + expect(group.groupMetadata.announce).to.equal(true); + + await helper.sleep(1000); + + // reload + group = await client.getChatById(group.id._serialized); + expect(group.groupMetadata.announce).to.equal(true); + }); + + it('can set all participants able to send messages', async function () { + expect(group.groupMetadata.announce).to.equal(true); + const res = await group.setMessagesAdminsOnly(false); + expect(res).to.equal(true); + expect(group.groupMetadata.announce).to.equal(false); + + await helper.sleep(1000); + + // reload + group = await client.getChatById(group.id._serialized); + expect(group.groupMetadata.announce).to.equal(false); + }); + + it('can set only admins able to set group info', async function () { + expect(group.groupMetadata.restrict).to.equal(false); + const res = await group.setInfoAdminsOnly(); + expect(res).to.equal(true); + expect(group.groupMetadata.restrict).to.equal(true); + + await helper.sleep(1000); + + // reload + group = await client.getChatById(group.id._serialized); + expect(group.groupMetadata.restrict).to.equal(true); + }); + + it('can set all participants able to set group info', async function () { + expect(group.groupMetadata.restrict).to.equal(true); + const res = await group.setInfoAdminsOnly(false); + expect(res).to.equal(true); + expect(group.groupMetadata.restrict).to.equal(false); + + await helper.sleep(1000); + + // reload + group = await client.getChatById(group.id._serialized); + expect(group.groupMetadata.restrict).to.equal(false); + }); + }); + + describe('Invites', function () { + it('can get the invite code', async function () { + const code = await group.getInviteCode(); + expect(typeof code).to.equal('string'); + }); + + it('can get invite info', async function () { + const code = await group.getInviteCode(); + const info = await client.getInviteInfo(code); + expect(info.id._serialized).to.equal(group.id._serialized); + expect(info.participants.length).to.equal(2); + }); + + it('can revoke the invite code', async function () { + const code = await group.getInviteCode(); + const newCode = await group.revokeInvite(); + expect(typeof newCode).to.equal('string'); + expect(newCode).to.not.equal(code); + }); + }); + + describe('Participants', function () { + it('can promote a user to admin', async function () { + let participant = group.participants.find(p => p.id._serialized === remoteId); + expect(participant).to.exist; + expect(participant.isAdmin).to.equal(false); + + const res = await group.promoteParticipants([remoteId]); + expect(res.status).to.be.greaterThanOrEqual(200); + + await helper.sleep(1000); + + // reload and check + group = await client.getChatById(group.id._serialized); + participant = group.participants.find(p => p.id._serialized=== remoteId); + expect(participant).to.exist; + expect(participant.isAdmin).to.equal(true); + }); + + it('can demote a user', async function () { + let participant = group.participants.find(p => p.id._serialized=== remoteId); + expect(participant).to.exist; + expect(participant.isAdmin).to.equal(true); + + const res = await group.demoteParticipants([remoteId]); + expect(res.status).to.be.greaterThanOrEqual(200); + + await helper.sleep(1000); + + // reload and check + group = await client.getChatById(group.id._serialized); + participant = group.participants.find(p => p.id._serialized=== remoteId); + expect(participant).to.exist; + expect(participant.isAdmin).to.equal(false); + }); + + it('can remove a user from the group', async function () { + let participant = group.participants.find(p => p.id._serialized=== remoteId); + expect(participant).to.exist; + + const res = await group.removeParticipants([remoteId]); + expect(res.status).to.be.greaterThanOrEqual(200); + + await helper.sleep(1000); + + // reload and check + group = await client.getChatById(group.id._serialized); + participant = group.participants.find(p => p.id._serialized=== remoteId); + expect(participant).to.not.exist; + }); + + it('can add back a user to the group', async function () { + let participant = group.participants.find(p => p.id._serialized=== remoteId); + expect(participant).to.not.exist; + + const res = await group.addParticipants([remoteId]); + expect(res.status).to.be.greaterThanOrEqual(200); + + await helper.sleep(1000); + + // reload and check + group = await client.getChatById(group.id._serialized); + participant = group.participants.find(p => p.id._serialized=== remoteId); + expect(participant).to.exist; + }); + }); + + describe('Leave / re-join', function () { + let code; + before(async function () { + code = await group.getInviteCode(); + }); + + it('can leave the group', async function () { + expect(group.isReadOnly).to.equal(false); + await group.leave(); + + await helper.sleep(1000); + + // reload and check + group = await client.getChatById(group.id._serialized); + expect(group.isReadOnly).to.equal(true); + }); + + it('can join a group via invite code', async function () { + const chatId = await client.acceptInvite(code); + expect(chatId).to.equal(group.id._serialized); + + await helper.sleep(1000); + + // reload and check + group = await client.getChatById(group.id._serialized); + expect(group.isReadOnly).to.equal(false); + }); + }); + + after(async function () { + await client.destroy(); + }); + +}); diff --git a/tests/structures/message.js b/tests/structures/message.js new file mode 100644 index 0000000..7a9749d --- /dev/null +++ b/tests/structures/message.js @@ -0,0 +1,112 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); + +const helper = require('../helper'); +const { Contact, Chat } = require('../../src/structures'); + +const remoteId = helper.remoteId; + +describe('Message', function () { + let client; + let chat; + let message; + + before(async function() { + this.timeout(35000); + client = helper.createClient({ authenticated: true }); + await client.initialize(); + + chat = await client.getChatById(remoteId); + message = await chat.sendMessage('this is only a test'); + + // wait for message to be sent + await helper.sleep(1000); + }); + + after(async function () { + await client.destroy(); + }); + + it('can get the related chat', async function () { + const chat = await message.getChat(); + expect(chat).to.be.instanceOf(Chat); + expect(chat.id._serialized).to.equal(remoteId); + }); + + it('can get the related contact', async function () { + const contact = await message.getContact(); + expect(contact).to.be.instanceOf(Contact); + expect(contact.id._serialized).to.equal(client.info.wid._serialized); + }); + + it('can get message info', async function () { + const info = await message.getInfo(); + expect(typeof info).to.equal('object'); + expect(Array.isArray(info.played)).to.equal(true); + expect(Array.isArray(info.read)).to.equal(true); + expect(Array.isArray(info.delivery)).to.equal(true); + }); + + describe('Replies', function () { + let replyMsg; + + it('can reply to a message', async function () { + replyMsg = await message.reply('this is my reply'); + expect(replyMsg.hasQuotedMsg).to.equal(true); + }); + + it('can get the quoted message', async function () { + const quotedMsg = await replyMsg.getQuotedMessage(); + expect(quotedMsg.id._serialized).to.equal(message.id._serialized); + }); + }); + + describe('Star', function () { + it('can star a message', async function () { + expect(message.isStarred).to.equal(false); + await message.star(); + + // reload and check + await message.reload(); + expect(message.isStarred).to.equal(true); + }); + + it('can un-star a message', async function () { + expect(message.isStarred).to.equal(true); + await message.unstar(); + + // reload and check + await message.reload(); + expect(message.isStarred).to.equal(false); + }); + }); + + describe('Delete', function () { + it('can delete a message for me', async function () { + await message.delete(); + + await helper.sleep(1000); + expect(await message.reload()).to.equal(null); + }); + + it('can delete a message for everyone', async function () { + message = await chat.sendMessage('sneaky message'); + await helper.sleep(1000); + + const callback = sinon.spy(); + client.once('message_revoke_everyone', callback); + + await message.delete(true); + await helper.sleep(1000); + + expect(await message.reload()).to.equal(null); + expect(callback.called).to.equal(true); + const [ revokeMsg, originalMsg ] = callback.args[0]; + expect(revokeMsg.id._serialized).to.equal(originalMsg.id._serialized); + expect(originalMsg.body).to.equal('sneaky message'); + expect(originalMsg.type).to.equal('chat'); + expect(revokeMsg.body).to.equal(''); + expect(revokeMsg.type).to.equal('revoked'); + }); + }); +}); \ No newline at end of file From f6de161c7d6a8870c3bcf75683df94e1c574c363 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Sun, 27 Feb 2022 22:02:49 -0400 Subject: [PATCH 09/50] Auth Strategies (#1257) * auth strategies * default to no auth * rename base auth strategy * rename base strategy cont. * refactor auth strategy methods and LocalAuth * activate old session options even if is falsy value * move restartOnAuthFail to LegacyAuthStrategy option * add link to guide item * update example/shell * types --- .gitignore | 5 +- .npmignore | 2 + example.js | 13 +-- index.d.ts | 80 ++++++++++++------ index.js | 6 ++ shell.js | 6 +- src/Client.js | 106 ++++++++---------------- src/authStrategies/BaseAuthStrategy.js | 24 ++++++ src/authStrategies/LegacySessionAuth.js | 72 ++++++++++++++++ src/authStrategies/LocalAuth.js | 54 ++++++++++++ src/authStrategies/NoAuth.js | 12 +++ src/util/Constants.js | 2 - 12 files changed, 266 insertions(+), 116 deletions(-) create mode 100644 src/authStrategies/BaseAuthStrategy.js create mode 100644 src/authStrategies/LegacySessionAuth.js create mode 100644 src/authStrategies/LocalAuth.js create mode 100644 src/authStrategies/NoAuth.js diff --git a/.gitignore b/.gitignore index 73c67dc..79f6467 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,4 @@ typings/ # Test sessions *session.json - -# user data -WWebJS/ -userDataDir/ \ No newline at end of file +.wwebjs_auth/ \ No newline at end of file diff --git a/.npmignore b/.npmignore index c68d77a..a5131c9 100644 --- a/.npmignore +++ b/.npmignore @@ -12,6 +12,8 @@ yarn-debug.log* yarn-error.log* *session.json +.wwebjs_auth/ + .env tools/ tests/ diff --git a/example.js b/example.js index bc3388e..021efb3 100644 --- a/example.js +++ b/example.js @@ -1,17 +1,10 @@ -const { Client, Location, List, Buttons } = require('./index'); +const { Client, Location, List, Buttons, LocalAuth } = require('./index'); -const client = new Client({ - clientId: 'example', +const client = new Client({ + authStrategy: new LocalAuth(), puppeteer: { headless: false } }); -// You also could connect to an existing instance of a browser -// { -// puppeteer: { -// browserWSEndpoint: `ws://localhost:3000` -// } -// } - client.initialize(); client.on('qr', (qr) => { diff --git a/index.d.ts b/index.d.ts index c3d1ab1..bdd7f54 100644 --- a/index.d.ts +++ b/index.d.ts @@ -157,8 +157,7 @@ declare namespace WAWebJS { /** Emitted when authentication is successful */ on(event: 'authenticated', listener: ( /** - * Object containing session information. Can be used to restore the session - * @deprecated + * Object containing session information, when using LegacySessionAuth. Can be used to restore the session */ session?: ClientSession ) => void): this @@ -297,35 +296,23 @@ declare namespace WAWebJS { /** Options for initializing the whatsapp client */ export interface ClientOptions { /** Timeout for authentication selector in puppeteer - * @default 45000 */ + * @default 0 */ authTimeoutMs?: number, /** Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ */ puppeteer?: puppeteer.LaunchOptions & puppeteer.BrowserLaunchArgumentOptions & puppeteer.BrowserConnectOptions - /** Refresh interval for qr code (how much time to wait before checking if the qr code has changed) - * @default 20000 */ - qrRefreshIntervalMs?: number - /** Timeout for qr code selector in puppeteer - * @default 45000 */ - qrTimeoutMs?: number, - /** How many times should the qrcode be refreshed before giving up + /** Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. */ + authStrategy?: AuthStrategy, + /** How many times should the qrcode be refreshed before giving up * @default 0 (disabled) */ qrMaxRetries?: number, - /** Restart client with a new session (i.e. use null 'session' var) if authentication fails - * @default false */ - restartOnAuthFail?: boolean - /** - * Enable authentication via a `session` option. - * @deprecated Will be removed in a future release - */ - useDeprecatedSessionAuth?: boolean /** - * WhatsApp session to restore. If not set, will start a new session - * @deprecated Set `useDeprecatedSessionAuth: true` to enable. This auth method is not supported by MultiDevice and will be removed in a future release. + * @deprecated This option should be set directly on the LegacySessionAuth + */ + restartOnAuthFail?: boolean + /** + * @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly. */ session?: ClientSession - /** Client id to distinguish instances if you are using multiple, otherwise keep empty if you are using only one instance - * @default '' */ - clientId: string /** If another whatsapp web session is detected (another browser), take over the session in the current browser * @default false */ takeoverOnConflict?: boolean, @@ -338,14 +325,53 @@ declare namespace WAWebJS { /** Ffmpeg path to use when formating videos to webp while sending stickers * @default 'ffmpeg' */ ffmpegPath?: string - /** Path to place session objects in - @default './WWebJS' */ - dataPath?: string + } + + /** + * Base class which all authentication strategies extend + */ + export abstract class AuthStrategy { + setup: (client: Client) => void; + beforeBrowserInitialized: () => Promise; + afterBrowserInitialized: () => Promise; + onAuthenticationNeeded: () => Promise<{ + failed?: boolean; + restart?: boolean; + failureEventPayload?: any + }>; + getAuthEventPayload: () => Promise; + logout: () => Promise; + } + + /** + * No session restoring functionality + * Will need to authenticate via QR code every time + */ + export class NoAuth extends AuthStrategy {} + + /** + * Local directory-based authentication + */ + export class LocalAuth extends AuthStrategy { + constructor(options?: { + clientId?: string, + dataPath?: string + }) + } + + /** + * Legacy session auth strategy + * Not compatible with multi-device accounts. + */ + export class LegacySessionAuth extends AuthStrategy { + constructor(options?: { + session?: ClientSession, + restartOnAuth?: boolean, + }) } /** * Represents a WhatsApp client session - * @deprecated */ export interface ClientSession { WABrowserId: string, diff --git a/index.js b/index.js index 3d2b64f..5a149e4 100644 --- a/index.js +++ b/index.js @@ -21,5 +21,11 @@ module.exports = { ProductMetadata: require('./src/structures/ProductMetadata'), List: require('./src/structures/List'), Buttons: require('./src/structures/Buttons'), + + // Auth Strategies + NoAuth: require('./src/authStrategies/NoAuth'), + LocalAuth: require('./src/authStrategies/LocalAuth'), + LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'), + ...Constants }; diff --git a/shell.js b/shell.js index 4f94c35..fd593a1 100644 --- a/shell.js +++ b/shell.js @@ -2,17 +2,17 @@ * ==== wwebjs-shell ==== * Used for quickly testing library features * - * Running `npm run shell` will start WhatsApp Web in headless mode + * Running `npm run shell` will start WhatsApp Web with headless=false * and then drop you into Node REPL with `client` in its context. */ const repl = require('repl'); -const { Client } = require('./index'); +const { Client, LocalAuth } = require('./index'); const client = new Client({ puppeteer: { headless: false }, - clientId: 'shell' + authStrategy: new LocalAuth() }); console.log('Initializing...'); diff --git a/src/Client.js b/src/Client.js index 5f908e0..30d9ea3 100644 --- a/src/Client.js +++ b/src/Client.js @@ -1,7 +1,5 @@ 'use strict'; -const path = require('path'); -const fs = require('fs'); const EventEmitter = require('events'); const puppeteer = require('puppeteer'); const moduleRaid = require('@pedroslopez/moduleraid/moduleraid'); @@ -13,23 +11,24 @@ const { ExposeStore, LoadUtils } = require('./util/Injected'); const ChatFactory = require('./factories/ChatFactory'); const ContactFactory = require('./factories/ContactFactory'); const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List } = require('./structures'); +const LegacySessionAuth = require('./authStrategies/LegacySessionAuth'); +const NoAuth = require('./authStrategies/NoAuth'); + /** * Starting point for interacting with the WhatsApp Web API * @extends {EventEmitter} * @param {object} options - Client options + * @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. * @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer * @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ * @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up - * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails - * @param {boolean} options.useDeprecatedSessionAuth - Enable JSON-based authentication. This is deprecated due to not being supported by MultiDevice, and will be removed in a future version. - * @param {object} options.session - This is deprecated due to not being supported by MultiDevice, and will be removed in a future version. + * @param {string} options.restartOnAuthFail - @deprecated This option should be set directly on the LegacySessionAuth. + * @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly. * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session - * @param {string} options.dataPath - Change the default path for saving session files, default is: "./WWebJS/" * @param {string} options.userAgent - User agent to use in puppeteer * @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy. - * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance * * @fires Client#qr * @fires Client#authenticated @@ -52,20 +51,28 @@ class Client extends EventEmitter { super(); this.options = Util.mergeDefault(DefaultOptions, options); + + if(!this.options.authStrategy) { + if(Object.prototype.hasOwnProperty.call(this.options, 'session')) { + process.emitWarning( + 'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' + + 'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).', + 'DeprecationWarning' + ); - this.id = this.options.clientId; - - // eslint-disable-next-line no-useless-escape - const foldernameRegex = /^(?!.{256,})(?!(aux|clock\$|con|nul|prn|com[1-9]|lpt[1-9])(?:$|\.))[^ ][ \.\w-$()+=[\];#@~,&']+[^\. ]$/i; - if (this.id && !foldernameRegex.test(this.id)) throw Error('Invalid client ID. Make sure you abide by the folder naming rules of your operating system.'); - - if (!this.options.useDeprecatedSessionAuth) { - this.dataDir = this.options.puppeteer.userDataDir; - const dirPath = path.join(process.cwd(), this.options.dataPath, this.id ? 'session-' + this.id : 'session'); - if (!this.dataDir) this.dataDir = dirPath; - fs.mkdirSync(this.dataDir, { recursive: true }); + this.authStrategy = new LegacySessionAuth({ + session: this.options.session, + restartOnAuthFail: this.options.restartOnAuthFail + }); + } else { + this.authStrategy = new NoAuth(); + } + } else { + this.authStrategy = this.options.authStrategy; } + this.authStrategy.setup(this); + this.pupBrowser = null; this.pupPage = null; @@ -78,10 +85,9 @@ class Client extends EventEmitter { async initialize() { let [browser, page] = [null, null]; - const puppeteerOpts = { - ...this.options.puppeteer, - userDataDir: this.options.useDeprecatedSessionAuth ? undefined : this.dataDir - }; + await this.authStrategy.beforeBrowserInitialized(); + + const puppeteerOpts = this.options.puppeteer; if (puppeteerOpts && puppeteerOpts.browserWSEndpoint) { browser = await puppeteer.connect(puppeteerOpts); page = await browser.newPage(); @@ -91,27 +97,12 @@ class Client extends EventEmitter { } await page.setUserAgent(this.options.userAgent); + if (this.options.bypassCSP) await page.setBypassCSP(true); this.pupBrowser = browser; this.pupPage = page; - if (this.options.useDeprecatedSessionAuth && this.options.session) { - await page.evaluateOnNewDocument(session => { - if (document.referrer === 'https://whatsapp.com/') { - localStorage.clear(); - localStorage.setItem('WABrowserId', session.WABrowserId); - localStorage.setItem('WASecretBundle', session.WASecretBundle); - localStorage.setItem('WAToken1', session.WAToken1); - localStorage.setItem('WAToken2', session.WAToken2); - } - - localStorage.setItem('remember-me', 'true'); - }, this.options.session); - } - - if (this.options.bypassCSP) { - await page.setBypassCSP(true); - } + await this.authStrategy.afterBrowserInitialized(); await page.goto(WhatsWebURL, { waitUntil: 'load', @@ -141,18 +132,17 @@ class Client extends EventEmitter { // Scan-qrcode selector was found. Needs authentication if (needAuthentication) { - if(this.options.session) { + const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); + if(failed) { /** * Emitted when there has been an error while trying to restore an existing session * @event Client#auth_failure * @param {string} message - * @deprecated */ - this.emit(Events.AUTHENTICATION_FAILURE, 'Unable to log in. Are the session details valid?'); + this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload); await this.destroy(); - if (this.options.restartOnAuthFail) { + if (restart) { // session restore failed so try again but without session to force new authentication - this.options.session = null; return this.initialize(); } return; @@ -224,20 +214,7 @@ class Client extends EventEmitter { } await page.evaluate(ExposeStore, moduleRaid.toString()); - let authEventPayload = undefined; - if (this.options.useDeprecatedSessionAuth) { - // Get session tokens - const localStorage = JSON.parse(await page.evaluate(() => { - return JSON.stringify(window.localStorage); - })); - - authEventPayload = { - WABrowserId: localStorage.WABrowserId, - WASecretBundle: localStorage.WASecretBundle, - WAToken1: localStorage.WAToken1, - WAToken2: localStorage.WAToken2 - }; - } + const authEventPayload = await this.authStrategy.getAuthEventPayload(); /** * Emitted when authentication is successful @@ -248,23 +225,14 @@ class Client extends EventEmitter { // Check window.Store Injection await page.waitForFunction('window.Store != undefined'); - const isMD = await page.evaluate(() => { - return window.Store.Features.features.MD_BACKEND; - }); - await page.evaluate(async () => { // safely unregister service workers const registrations = await navigator.serviceWorker.getRegistrations(); for (let registration of registrations) { registration.unregister(); } - }); - if (this.options.useDeprecatedSessionAuth && isMD) { - throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.'); - } - //Load util functions (serializers, helper functions) await page.evaluate(LoadUtils); @@ -518,9 +486,7 @@ class Client extends EventEmitter { return window.Store.AppState.logout(); }); - if (this.dataDir) { - return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this.dataDir, { recursive: true }); - } + await this.authStrategy.logout(); } /** diff --git a/src/authStrategies/BaseAuthStrategy.js b/src/authStrategies/BaseAuthStrategy.js new file mode 100644 index 0000000..0c7a7c9 --- /dev/null +++ b/src/authStrategies/BaseAuthStrategy.js @@ -0,0 +1,24 @@ +'use strict'; + +/** + * Base class which all authentication strategies extend + */ +class BaseAuthStrategy { + constructor() {} + setup(client) { + this.client = client; + } + async beforeBrowserInitialized() {} + async afterBrowserInitialized() {} + async onAuthenticationNeeded() { + return { + failed: false, + restart: false, + failureEventPayload: undefined + }; + } + async getAuthEventPayload() {} + async logout() {} +} + +module.exports = BaseAuthStrategy; \ No newline at end of file diff --git a/src/authStrategies/LegacySessionAuth.js b/src/authStrategies/LegacySessionAuth.js new file mode 100644 index 0000000..58332ac --- /dev/null +++ b/src/authStrategies/LegacySessionAuth.js @@ -0,0 +1,72 @@ +'use strict'; + +const BaseAuthStrategy = require('./BaseAuthStrategy'); + +/** + * Legacy session auth strategy + * Not compatible with multi-device accounts. + * @param {object} options - options + * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails + * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session + * @param {string} options.session.WABrowserId + * @param {string} options.session.WASecretBundle + * @param {string} options.session.WAToken1 + * @param {string} options.session.WAToken2 + */ +class LegacySessionAuth extends BaseAuthStrategy { + constructor({ session, restartOnAuthFail }) { + super(); + this.session = session; + this.restartOnAuthFail = restartOnAuthFail; + } + + async afterBrowserInitialized() { + if(this.session) { + await this.client.pupPage.evaluateOnNewDocument(session => { + if (document.referrer === 'https://whatsapp.com/') { + localStorage.clear(); + localStorage.setItem('WABrowserId', session.WABrowserId); + localStorage.setItem('WASecretBundle', session.WASecretBundle); + localStorage.setItem('WAToken1', session.WAToken1); + localStorage.setItem('WAToken2', session.WAToken2); + } + + localStorage.setItem('remember-me', 'true'); + }, this.session); + } + } + + async onAuthenticationNeeded() { + if(this.session) { + this.session = null; + return { + failed: true, + restart: this.restartOnAuthFail, + failureEventPayload: 'Unable to log in. Are the session details valid?' + }; + } + + return { failed: false }; + } + + async getAuthEventPayload() { + const isMD = await this.client.pupPage.evaluate(() => { + return window.Store.Features.features.MD_BACKEND; + }); + + if(isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.'); + + const localStorage = JSON.parse(await this.client.pupPage.evaluate(() => { + return JSON.stringify(window.localStorage); + })); + + return { + WABrowserId: localStorage.WABrowserId, + WASecretBundle: localStorage.WASecretBundle, + WAToken1: localStorage.WAToken1, + WAToken2: localStorage.WAToken2 + }; + } +} + +module.exports = LegacySessionAuth; \ No newline at end of file diff --git a/src/authStrategies/LocalAuth.js b/src/authStrategies/LocalAuth.js new file mode 100644 index 0000000..1f9770d --- /dev/null +++ b/src/authStrategies/LocalAuth.js @@ -0,0 +1,54 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const BaseAuthStrategy = require('./BaseAuthStrategy'); + +/** + * Local directory-based authentication + * @param {object} options - options + * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance + * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" +*/ +class LocalAuth extends BaseAuthStrategy { + constructor({ clientId, dataPath }={}) { + super(); + + const idRegex = /^[-_\w]+$/i; + if(clientId && !idRegex.test(clientId)) { + throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.'); + } + + this.dataPath = path.resolve(dataPath || './.wwebjs_auth/'); + this.clientId = clientId; + } + + async beforeBrowserInitialized() { + const puppeteerOpts = this.client.options.puppeteer; + + if(puppeteerOpts.userDataDir) { + throw new Error('LocalAuth is not compatible with a user-supplied userDataDir.'); + } + + const sessionDirName = this.clientId ? `session-${this.clientId}` : 'session'; + const dirPath = path.join(this.dataPath, sessionDirName); + + fs.mkdirSync(dirPath, { recursive: true }); + + this.client.options.puppeteer = { + ...puppeteerOpts, + userDataDir: dirPath + }; + + this.userDataDir = dirPath; + } + + async logout() { + if (this.userDataDir) { + return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this.userDataDir, { recursive: true }); + } + } + +} + +module.exports = LocalAuth; \ No newline at end of file diff --git a/src/authStrategies/NoAuth.js b/src/authStrategies/NoAuth.js new file mode 100644 index 0000000..1e11a2c --- /dev/null +++ b/src/authStrategies/NoAuth.js @@ -0,0 +1,12 @@ +'use strict'; + +const BaseAuthStrategy = require('./BaseAuthStrategy'); + +/** + * No session restoring functionality + * Will need to authenticate via QR code every time +*/ +class NoAuth extends BaseAuthStrategy { } + + +module.exports = NoAuth; \ No newline at end of file diff --git a/src/util/Constants.js b/src/util/Constants.js index 1b27ca5..83a030f 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -7,8 +7,6 @@ exports.DefaultOptions = { headless: true, defaultViewport: null }, - dataPath: './WWebJS/', - useDeprecatedSessionAuth: false, authTimeoutMs: 0, qrMaxRetries: 0, takeoverOnConflict: false, From 9fe91692cf409cf8ac8e2393f09b6d3cb6570830 Mon Sep 17 00:00:00 2001 From: Pedro Lopez Date: Sun, 27 Feb 2022 22:18:54 -0400 Subject: [PATCH 10/50] update tests to work with authStrategies --- tests/client.js | 149 ++++++++++++++++++++++++------------------------ tests/helper.js | 26 ++++----- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/tests/client.js b/tests/client.js index c99d672..7bfcc1b 100644 --- a/tests/client.js +++ b/tests/client.js @@ -9,6 +9,7 @@ const Message = require('../src/structures/Message'); const MessageMedia = require('../src/structures/MessageMedia'); const Location = require('../src/structures/Location'); const { MessageTypes, WAState } = require('../src/util/Constants'); +const { LegacySessionAuth } = require('../src/authStrategies/LegacySessionAuth'); const expect = chai.expect; chai.use(chaiAsPromised); @@ -71,7 +72,7 @@ describe('Client', function() { expect(authenticatedCallback.called).to.equal(true); - if(helper.isUsingDeprecatedSession()) { + if(helper.isUsingLegacySession()) { const newSession = authenticatedCallback.args[0][0]; expect(newSession).to.have.key([ 'WABrowserId', @@ -87,6 +88,80 @@ describe('Client', function() { await client.destroy(); }); + describe('LegacySessionAuth', function () { + it('should fail auth if session is invalid', async function() { + this.timeout(40000); + + const authFailCallback = sinon.spy(); + const qrCallback = sinon.spy(); + const readyCallback = sinon.spy(); + + const client = helper.createClient({ + options: { + authStrategy: new LegacySessionAuth({ + session: { + WABrowserId: 'invalid', + WASecretBundle: 'invalid', + WAToken1: 'invalid', + WAToken2: 'invalid' + }, + restartOnAuthFail: false, + }), + } + }); + + client.on('qr', qrCallback); + client.on('auth_failure', authFailCallback); + client.on('ready', readyCallback); + + client.initialize(); + + await helper.sleep(25000); + + expect(authFailCallback.called).to.equal(true); + expect(authFailCallback.args[0][0]).to.equal('Unable to log in. Are the session details valid?'); + + expect(readyCallback.called).to.equal(false); + expect(qrCallback.called).to.equal(false); + + await client.destroy(); + }); + + it('can restart without a session if session was invalid and restartOnAuthFail=true', async function() { + this.timeout(40000); + + const authFailCallback = sinon.spy(); + const qrCallback = sinon.spy(); + + const client = helper.createClient({ + options: { + authStrategy: new LegacySessionAuth({ + session: { + WABrowserId: 'invalid', + WASecretBundle: 'invalid', + WAToken1: 'invalid', + WAToken2: 'invalid' + }, + restartOnAuthFail: true, + }), + } + }); + + client.on('auth_failure', authFailCallback); + client.on('qr', qrCallback); + + client.initialize(); + + await helper.sleep(35000); + + expect(authFailCallback.called).to.equal(true); + expect(qrCallback.called).to.equal(true); + expect(qrCallback.args[0][0]).to.have.lengthOf(152); + + await client.destroy(); + }); + }); + describe('Non-MD only', function () { if(!isMD) { it('can take over if client was logged in somewhere else with takeoverOnConflict=true', async function() { @@ -127,78 +202,6 @@ describe('Client', function() { await client1.destroy(); }); - - it('should fail auth if session is invalid', async function() { - this.timeout(40000); - - const authFailCallback = sinon.spy(); - const qrCallback = sinon.spy(); - const readyCallback = sinon.spy(); - - const client = helper.createClient({ - options: { - session: { - WABrowserId: 'invalid', - WASecretBundle: 'invalid', - WAToken1: 'invalid', - WAToken2: 'invalid' - }, - authTimeoutMs: 10000, - restartOnAuthFail: false, - useDeprecatedSessionAuth: true - } - }); - - client.on('qr', qrCallback); - client.on('auth_failure', authFailCallback); - client.on('ready', readyCallback); - - client.initialize(); - - await helper.sleep(25000); - - expect(authFailCallback.called).to.equal(true); - expect(authFailCallback.args[0][0]).to.equal('Unable to log in. Are the session details valid?'); - - expect(readyCallback.called).to.equal(false); - expect(qrCallback.called).to.equal(false); - - await client.destroy(); - }); - - it('can restart without a session if session was invalid and restartOnAuthFail=true', async function() { - this.timeout(40000); - - const authFailCallback = sinon.spy(); - const qrCallback = sinon.spy(); - - const client = helper.createClient({ - options:{ - session: { - WABrowserId: 'invalid', - WASecretBundle: 'invalid', - WAToken1: 'invalid', - WAToken2: 'invalid' - }, - authTimeoutMs: 10000, - restartOnAuthFail: true, - useDeprecatedSessionAuth: true - } - }); - - client.on('auth_failure', authFailCallback); - client.on('qr', qrCallback); - - client.initialize(); - - await helper.sleep(35000); - - expect(authFailCallback.called).to.equal(true); - expect(qrCallback.called).to.equal(true); - expect(qrCallback.args[0][0]).to.have.lengthOf(152); - - await client.destroy(); - }); } }); }); diff --git a/tests/helper.js b/tests/helper.js index bcd22a3..fc9ddae 100644 --- a/tests/helper.js +++ b/tests/helper.js @@ -1,13 +1,12 @@ const path = require('path'); -const crypto = require('crypto'); -const Client = require('../src/Client'); +const { Client, LegacySessionAuth, LocalAuth } = require('..'); require('dotenv').config(); const remoteId = process.env.WWEBJS_TEST_REMOTE_ID; if(!remoteId) throw new Error('The WWEBJS_TEST_REMOTE_ID environment variable has not been set.'); -function isUsingDeprecatedSession() { +function isUsingLegacySession() { return Boolean(process.env.WWEBJS_TEST_SESSION || process.env.WWEBJS_TEST_SESSION_PATH); } @@ -15,10 +14,10 @@ function isMD() { return Boolean(process.env.WWEBJS_TEST_MD); } -if(isUsingDeprecatedSession() && isMD()) throw 'Cannot use deprecated sessions with WWEBJS_TEST_MD=true'; +if(isUsingLegacySession() && isMD()) throw 'Cannot use legacy sessions with WWEBJS_TEST_MD=true'; function getSessionFromEnv() { - if (!isUsingDeprecatedSession()) return null; + if (!isUsingLegacySession()) return null; const envSession = process.env.WWEBJS_TEST_SESSION; if(envSession) return JSON.parse(envSession); @@ -34,17 +33,18 @@ function createClient({authenticated, options: additionalOpts}={}) { const options = {}; if(authenticated) { - const deprecatedSession = getSessionFromEnv(); - if(deprecatedSession) { - options.session = deprecatedSession; - options.useDeprecatedSessionAuth = true; + const legacySession = getSessionFromEnv(); + if(legacySession) { + options.authStrategy = new LegacySessionAuth({ + session: legacySession + }); } else { const clientId = process.env.WWEBJS_TEST_CLIENT_ID; if(!clientId) throw new Error('No session found in environment.'); - options.clientId = clientId; + options.authStrategy = new LocalAuth({ + clientId + }); } - } else { - options.clientId = crypto.randomBytes(5).toString('hex'); } const allOpts = {...options, ...(additionalOpts || {})}; @@ -58,7 +58,7 @@ function sleep(ms) { module.exports = { sleep, createClient, - isUsingDeprecatedSession, + isUsingLegacySession, isMD, remoteId, }; \ No newline at end of file From 52c8336236d7df476320f57532b4ba37803b4df7 Mon Sep 17 00:00:00 2001 From: YaronD <47784310+Yarondr@users.noreply.github.com> Date: Mon, 28 Feb 2022 04:30:47 +0200 Subject: [PATCH 11/50] Added message duration in seconds (#1230) Co-authored-by: Pedro S. Lopez --- index.d.ts | 2 ++ src/structures/Message.js | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/index.d.ts b/index.d.ts index bdd7f54..70e7d99 100644 --- a/index.d.ts +++ b/index.d.ts @@ -615,6 +615,8 @@ declare namespace WAWebJS { hasMedia: boolean, /** Indicates if the message was sent as a reply to another message */ hasQuotedMsg: boolean, + /** Indicates the duration of the message in seconds */ + duration: string, /** ID that represents the message */ id: MessageId, /** Indicates if the message was forwarded */ diff --git a/src/structures/Message.js b/src/structures/Message.js index 6a326b9..ea541fa 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -134,6 +134,12 @@ class Message extends Base { */ this.hasQuotedMsg = data.quotedMsg ? true : false; + /** + * Indicates the duration of the message in seconds + * @type {string} + */ + this.duration = data.duration ? data.duration : undefined; + /** * Location information contained in the message, if the message is type "location" * @type {Location} From ab5167c4ade7365a92292aac00f02e27e60fd97b Mon Sep 17 00:00:00 2001 From: SuperChang Date: Mon, 28 Feb 2022 10:34:12 +0800 Subject: [PATCH 12/50] fix: `ChangeParticipantsPermissions` typo in index.d.ts (#1206) Co-authored-by: Pedro S. Lopez --- index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 70e7d99..b391515 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1048,7 +1048,7 @@ declare namespace WAWebJS { } /** Promotes or demotes participants by IDs to regular users or admins */ - export type ChangeParticipantsPermisions = + export type ChangeParticipantsPermissions = (participantIds: Array) => Promise<{ status: number }> /** Adds or removes a list of participants by ID to the group */ @@ -1078,9 +1078,9 @@ declare namespace WAWebJS { /** Removes a list of participants by ID to the group */ removeParticipants: ChangeGroupParticipants; /** Promotes participants by IDs to admins */ - promoteParticipants: ChangeParticipantsPermisions; + promoteParticipants: ChangeParticipantsPermissions; /** Demotes participants by IDs to regular users */ - demoteParticipants: ChangeParticipantsPermisions; + demoteParticipants: ChangeParticipantsPermissions; /** Updates the group subject */ setSubject: (subject: string) => Promise; /** Updates the group description */ From 12d9735b73a2f26bd69d3d0d81f2b944bb849711 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Sun, 27 Feb 2022 22:58:39 -0400 Subject: [PATCH 13/50] Update README.md --- README.md | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 3058048..2beef9a 100644 --- a/README.md +++ b/README.md @@ -47,43 +47,14 @@ client.initialize(); Take a look at [example.js](https://github.com/pedroslopez/whatsapp-web.js/blob/master/example.js) for another example with more use cases. -## Remote Access +For more information on saving and restoring sessions, check out the available [Authentication Strategies](https://wwebjs.dev/guide/authentication.html). -You could also connect to any previously existing browser instance: - -```js -const client = new Client({ - puppeteer: { - browserWSEndpoint: `ws://localhost:3000` - } -}); -``` - -### Docker - -1) Installing a browser using browserless: - -``` -docker run \ - --rm \ - -p 3000:3000 \ - -e "MAX_CONCURRENT_SESSIONS=1" \ - browserless/chrome:latest -``` - -Reference: https://docs.browserless.io/docs/docker-quickstart.html - -### Remote Debugging - -2) Running a browser with websocket remote debugging enabled: -> chrome.exe --remote-debugging-port=9222 - -After that check the following webpage and check http://127.0.0.1:9220/json and get the **webSocketDebuggerUrl** ## Supported features | Feature | Status | | ------------- | ------------- | +| Multi Device | ✅ | | Send messages | ✅ | | Receive messages | ✅ | | Send media (images/audio/documents) | ✅ | @@ -116,11 +87,13 @@ Something missing? Make an issue and let us know! Pull requests are welcome! If you see something you'd like to add, please do. For drastic changes, please open an issue first. -## Donating +## Supporting the project -You can support the maintainer of this project through the link below +You can support the maintainer of this project through the links below -[![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://www.paypal.me/psla/) +- [Support via GitHub Sponsors](https://github.com/sponsors/pedroslopez) +- [Support via PayPal](https://www.paypal.me/psla/) +- [Sign up for DigitalOcean](https://m.do.co/c/73f906a36ed4) and get $100 in credit when you sign up (Referral) ## Disclaimer From 6691d25282a94376da533e93f25b865a890c1716 Mon Sep 17 00:00:00 2001 From: Pedro Lopez Date: Sun, 27 Feb 2022 23:28:57 -0400 Subject: [PATCH 14/50] fix: message.getInfo() works for both MD and non-MD --- src/structures/Message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index ea541fa..d8ea5ba 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -475,7 +475,7 @@ class Message extends Base { const msg = window.Store.Msg.get(msgId); if (!msg) return null; - return await window.Store.MessageInfo.sendQueryMsgInfo(msg.id); + return await window.Store.MessageInfo.sendQueryMsgInfo(msg); }, this.id._serialized); return info; From 1f4328c7a3751b69ed5894e14e78e6160390cc8f Mon Sep 17 00:00:00 2001 From: Pedro Lopez Date: Sun, 27 Feb 2022 23:29:09 -0400 Subject: [PATCH 15/50] fix tests --- tests/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/client.js b/tests/client.js index 7bfcc1b..f01412f 100644 --- a/tests/client.js +++ b/tests/client.js @@ -8,8 +8,8 @@ const Contact = require('../src/structures/Contact'); const Message = require('../src/structures/Message'); const MessageMedia = require('../src/structures/MessageMedia'); const Location = require('../src/structures/Location'); +const LegacySessionAuth = require('../src/authStrategies/LegacySessionAuth'); const { MessageTypes, WAState } = require('../src/util/Constants'); -const { LegacySessionAuth } = require('../src/authStrategies/LegacySessionAuth'); const expect = chai.expect; chai.use(chaiAsPromised); From 979e97ea425b87a8aea7269519f99b7126aac573 Mon Sep 17 00:00:00 2001 From: Pedro Lopez Date: Sun, 27 Feb 2022 23:30:03 -0400 Subject: [PATCH 16/50] chore: mark version v1.16.0 --- docs/Base.html | 6 +- docs/BaseAuthStrategy.html | 65 +++ docs/BusinessContact.html | 24 +- docs/Buttons.html | 6 +- docs/Call.html | 6 +- docs/Chat.html | 6 +- docs/Client.html | 280 ++++--------- docs/Client.js.html | 392 ++++++++++-------- docs/ClientInfo.html | 35 +- docs/Contact.html | 24 +- docs/GroupChat.html | 24 +- docs/GroupNotification.html | 6 +- docs/InterfaceController.html | 6 +- docs/Label.html | 6 +- docs/LegacySessionAuth.html | 188 +++++++++ docs/List.html | 6 +- docs/LocalAuth.html | 135 ++++++ docs/Location.html | 6 +- docs/Message.html | 41 +- docs/MessageMedia.html | 6 +- docs/NoAuth.html | 66 +++ docs/Order.html | 6 +- docs/PrivateChat.html | 6 +- docs/PrivateContact.html | 24 +- docs/Product.html | 6 +- docs/Util.html | 8 +- docs/authStrategies_BaseAuthStrategy.js.html | 77 ++++ docs/authStrategies_LegacySessionAuth.js.html | 125 ++++++ docs/authStrategies_LocalAuth.js.html | 107 +++++ docs/authStrategies_NoAuth.js.html | 65 +++ docs/global.html | 8 +- docs/index.html | 257 ++++++++---- docs/scripts/jsdoc-toc.js | 2 +- docs/structures_Base.js.html | 6 +- docs/structures_BusinessContact.js.html | 6 +- docs/structures_Buttons.js.html | 6 +- docs/structures_Call.js.html | 6 +- docs/structures_Chat.js.html | 6 +- docs/structures_ClientInfo.js.html | 19 +- docs/structures_Contact.js.html | 17 +- docs/structures_GroupChat.js.html | 111 +++-- docs/structures_GroupNotification.js.html | 6 +- docs/structures_Label.js.html | 6 +- docs/structures_List.js.html | 6 +- docs/structures_Location.js.html | 6 +- docs/structures_Message.js.html | 95 +++-- docs/structures_MessageMedia.js.html | 6 +- docs/structures_Order.js.html | 6 +- docs/structures_Payment.js.html | 6 +- docs/structures_PrivateChat.js.html | 6 +- docs/structures_PrivateContact.js.html | 6 +- docs/structures_Product.js.html | 6 +- docs/structures_ProductMetadata.js.html | 6 +- docs/util_Constants.js.html | 11 +- docs/util_InterfaceController.js.html | 6 +- docs/util_Util.js.html | 60 +-- package.json | 2 +- 57 files changed, 1719 insertions(+), 723 deletions(-) create mode 100644 docs/BaseAuthStrategy.html create mode 100644 docs/LegacySessionAuth.html create mode 100644 docs/LocalAuth.html create mode 100644 docs/NoAuth.html create mode 100644 docs/authStrategies_BaseAuthStrategy.js.html create mode 100644 docs/authStrategies_LegacySessionAuth.js.html create mode 100644 docs/authStrategies_LocalAuth.js.html create mode 100644 docs/authStrategies_NoAuth.js.html diff --git a/docs/Base.html b/docs/Base.html index 9e3376a..8d543df 100644 --- a/docs/Base.html +++ b/docs/Base.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Base + whatsapp-web.js 1.16.0 » Class: Base @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@
diff --git a/docs/BaseAuthStrategy.html b/docs/BaseAuthStrategy.html new file mode 100644 index 0000000..44b1f1a --- /dev/null +++ b/docs/BaseAuthStrategy.html @@ -0,0 +1,65 @@ + + + + + + + whatsapp-web.js 1.16.0 » Class: BaseAuthStrategy + + + + + + + + +
+
+
+
+ +
+
+
+

new BaseAuthStrategy()

+
+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/BusinessContact.html b/docs/BusinessContact.html index 2a3b10c..81ec1f6 100644 --- a/docs/BusinessContact.html +++ b/docs/BusinessContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: BusinessContact + whatsapp-web.js 1.16.0 » Class: BusinessContact @@ -15,7 +15,7 @@ @@ -111,19 +111,22 @@
+
getProfilePicUrl()
+
+
unblock()
@@ -269,6 +272,15 @@
async
+

getCommonGroups() → Promise containing Array of WAWebJS.ChatId

+

Gets the Contact's common groups with you. Returns empty array if you don't have any common group.

+
+
Inherited from
+
Contact#getCommonGroups
+
Returns
+
+
+
async

getCountryCode() → Promise containing string

Returns the contact's countrycode, (1541859685@c.us) => (1)

@@ -314,7 +326,7 @@
diff --git a/docs/Buttons.html b/docs/Buttons.html index 642cdc3..f03eac7 100644 --- a/docs/Buttons.html +++ b/docs/Buttons.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Buttons + whatsapp-web.js 1.16.0 » Class: Buttons @@ -15,7 +15,7 @@ @@ -234,7 +234,7 @@ Returns: [{ buttonId:'customId',buttonText:{'displayText':&#
diff --git a/docs/Call.html b/docs/Call.html index de50a16..432b0cd 100644 --- a/docs/Call.html +++ b/docs/Call.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Call + whatsapp-web.js 1.16.0 » Class: Call @@ -15,7 +15,7 @@ @@ -144,7 +144,7 @@
diff --git a/docs/Chat.html b/docs/Chat.html index 1a9cd4b..c2afeea 100644 --- a/docs/Chat.html +++ b/docs/Chat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Chat + whatsapp-web.js 1.16.0 » Class: Chat @@ -15,7 +15,7 @@ @@ -483,7 +483,7 @@
diff --git a/docs/Client.html b/docs/Client.html index 0d30a80..dac8f52 100644 --- a/docs/Client.html +++ b/docs/Client.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Client + whatsapp-web.js 1.16.0 » Class: Client @@ -15,7 +15,7 @@ @@ -26,7 +26,7 @@
async
+

getCommonGroups(contactId) → Promise containing Array of WAWebJS.ChatId

+

Gets the Contact's common groups with you. Returns empty array if you don't have any common group.

+
+

Parameter

+ + + + + + + + + + + + + + + + + +
NameTypeOptionalDescription
+

contactId

+
+

string

+
+

 

+
+

the whatsapp user's ID (_serialized format)

+
+
+
+
Returns
+
+

Promise containing Array of WAWebJS.ChatId 

+
+
+
async

getContactById(contactId) → Promise containing Contact

Get contact instance by ID

@@ -1452,6 +1429,11 @@
async
+

sendPresenceUnavailable()

+

Marks the client as unavailable

+
+
+
async

sendSeen(chatId) → Promise containing boolean

Mark as seen for the Chat

@@ -1491,7 +1473,7 @@
async
-

setDisplayName(displayName)

+

setDisplayName(displayName) → Promise containing Boolean

Sets the current user's display name. This is the name shown to WhatsApp users that have not added you as a contact beside your number in groups and in your profile.

@@ -1524,6 +1506,10 @@
+
Returns
+
+

Promise containing Boolean 

+
async

setStatus(status)

@@ -1650,104 +1636,10 @@

authenticated

Emitted when authentication is successful

-
-

Parameters

- - - - - - - - - - - - - - - - - -
NameTypeOptionalDescription
-

session

-
-

object

-
-

 

-
-

Object containing session information. Can be used to restore the session.

-

Values in session have the following properties:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeOptionalDescription
-

WABrowserId

-
-

string

-
-

 

-
-
-

WASecretBundle

-
-

string

-
-

 

-
-
-

WAToken1

-
-

string

-
-

 

-
-
-

WAToken2

-
-

string

-
-

 

-
-
-
-

change_battery

-

Emitted when the battery percentage for the attached device changes

+

Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.

Parameters

@@ -1818,6 +1710,8 @@
+
Deprecated
+

change_state

Emitted when the connection state changes

@@ -2370,7 +2264,7 @@

qr

-

Emitted when the QR code is received

+

Emitted when a QR code is received

Parameter

@@ -2416,7 +2310,7 @@
diff --git a/docs/Client.js.html b/docs/Client.js.html index 74260b7..929584b 100644 --- a/docs/Client.js.html +++ b/docs/Client.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Source: Client.js + whatsapp-web.js 1.16.0 » Source: Client.js @@ -15,7 +15,7 @@ @@ -34,7 +34,6 @@ const EventEmitter = require('events'); const puppeteer = require('puppeteer'); const moduleRaid = require('@pedroslopez/moduleraid/moduleraid'); -const jsQR = require('jsqr'); const Util = require('./util/Util'); const InterfaceController = require('./util/InterfaceController'); @@ -42,22 +41,20 @@ const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./ut const { ExposeStore, LoadUtils } = require('./util/Injected'); const ChatFactory = require('./factories/ChatFactory'); const ContactFactory = require('./factories/ContactFactory'); -const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification , Label, Call, Buttons, List} = require('./structures'); +const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List } = require('./structures'); +const LegacySessionAuth = require('./authStrategies/LegacySessionAuth'); +const NoAuth = require('./authStrategies/NoAuth'); + /** * Starting point for interacting with the WhatsApp Web API * @extends {EventEmitter} * @param {object} options - Client options + * @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. * @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer * @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ - * @param {number} options.qrRefreshIntervalMs - Refresh interval for qr code (how much time to wait before checking if the qr code has changed) - * @param {number} options.qrTimeoutMs - Timeout for qr code selector in puppeteer * @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up - * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails - * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session - * @param {string} options.session.WABrowserId - * @param {string} options.session.WASecretBundle - * @param {string} options.session.WAToken1 - * @param {string} options.session.WAToken2 + * @param {string} options.restartOnAuthFail - @deprecated This option should be set directly on the LegacySessionAuth. + * @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly. * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session * @param {string} options.userAgent - User agent to use in puppeteer @@ -79,13 +76,33 @@ const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification * @fires Client#group_update * @fires Client#disconnected * @fires Client#change_state - * @fires Client#change_battery */ class Client extends EventEmitter { constructor(options = {}) { super(); this.options = Util.mergeDefault(DefaultOptions, options); + + if(!this.options.authStrategy) { + if(Object.prototype.hasOwnProperty.call(this.options, 'session')) { + process.emitWarning( + 'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' + + 'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).', + 'DeprecationWarning' + ); + + this.authStrategy = new LegacySessionAuth({ + session: this.options.session, + restartOnAuthFail: this.options.restartOnAuthFail + }); + } else { + this.authStrategy = new NoAuth(); + } + } else { + this.authStrategy = this.options.authStrategy; + } + + this.authStrategy.setup(this); this.pupBrowser = null; this.pupPage = null; @@ -98,41 +115,25 @@ class Client extends EventEmitter { */ async initialize() { let [browser, page] = [null, null]; - - if(this.options.puppeteer &amp;&amp; this.options.puppeteer.browserWSEndpoint) { - browser = await puppeteer.connect(this.options.puppeteer); + + await this.authStrategy.beforeBrowserInitialized(); + + const puppeteerOpts = this.options.puppeteer; + if (puppeteerOpts &amp;&amp; puppeteerOpts.browserWSEndpoint) { + browser = await puppeteer.connect(puppeteerOpts); page = await browser.newPage(); } else { - browser = await puppeteer.launch(this.options.puppeteer); + browser = await puppeteer.launch(puppeteerOpts); page = (await browser.pages())[0]; } - + await page.setUserAgent(this.options.userAgent); + if (this.options.bypassCSP) await page.setBypassCSP(true); this.pupBrowser = browser; this.pupPage = page; - // remember me - await page.evaluateOnNewDocument(() => { - localStorage.setItem('remember-me', 'true'); - }); - - if (this.options.session) { - await page.evaluateOnNewDocument( - session => { - if(document.referrer === 'https://whatsapp.com/') { - localStorage.clear(); - localStorage.setItem('WABrowserId', session.WABrowserId); - localStorage.setItem('WASecretBundle', session.WASecretBundle); - localStorage.setItem('WAToken1', session.WAToken1); - localStorage.setItem('WAToken2', session.WAToken2); - } - }, this.options.session); - } - - if(this.options.bypassCSP) { - await page.setBypassCSP(true); - } + await this.authStrategy.afterBrowserInitialized(); await page.goto(WhatsWebURL, { waitUntil: 'load', @@ -140,56 +141,54 @@ class Client extends EventEmitter { referer: 'https://whatsapp.com/' }); - const KEEP_PHONE_CONNECTED_IMG_SELECTOR = '[data-icon="intro-md-beta-logo-dark"], [data-icon="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"]'; + const INTRO_IMG_SELECTOR = '[data-testid="intro-md-beta-logo-dark"], [data-testid="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"]'; + const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas'; - if (this.options.session) { - // Check if session restore was successful - try { - await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: this.options.authTimeoutMs }); - } catch (err) { - if (err.name === 'TimeoutError') { - /** - * Emitted when there has been an error while trying to restore an existing session - * @event Client#auth_failure - * @param {string} message - */ - this.emit(Events.AUTHENTICATION_FAILURE, 'Unable to log in. Are the session details valid?'); - browser.close(); - if (this.options.restartOnAuthFail) { - // session restore failed so try again but without session to force new authentication - this.options.session = null; - this.initialize(); - } - return; + // Checks which selector appears first + const needAuthentication = await Promise.race([ + new Promise(resolve => { + page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs }) + .then(() => resolve(false)) + .catch((err) => resolve(err)); + }), + new Promise(resolve => { + page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs }) + .then(() => resolve(true)) + .catch((err) => resolve(err)); + }) + ]); + + // Checks if an error ocurred on the first found selector. The second will be discarded and ignored by .race; + if (needAuthentication instanceof Error) throw needAuthentication; + + // Scan-qrcode selector was found. Needs authentication + if (needAuthentication) { + const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); + if(failed) { + /** + * Emitted when there has been an error while trying to restore an existing session + * @event Client#auth_failure + * @param {string} message + */ + this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload); + await this.destroy(); + if (restart) { + // session restore failed so try again but without session to force new authentication + return this.initialize(); } - - throw err; + return; } - } else { + const QR_CONTAINER = 'div[data-ref]'; + const QR_RETRY_BUTTON = 'div[data-ref] > span > button'; let qrRetries = 0; - - const getQrCode = async () => { - // Check if retry button is present - var QR_RETRY_SELECTOR = 'div[data-ref] > span > button'; - var qrRetry = await page.$(QR_RETRY_SELECTOR); - if (qrRetry) { - await qrRetry.click(); - } - - // Wait for QR Code - const QR_CANVAS_SELECTOR = 'canvas'; - await page.waitForSelector(QR_CANVAS_SELECTOR, { timeout: this.options.qrTimeoutMs }); - const qrImgData = await page.$eval(QR_CANVAS_SELECTOR, canvas => [].slice.call(canvas.getContext('2d').getImageData(0, 0, 264, 264).data)); - const qr = jsQR(qrImgData, 264, 264).data; - + await page.exposeFunction('qrChanged', async (qr) => { /** - * Emitted when the QR code is received + * Emitted when a QR code is received * @event Client#qr * @param {string} qr QR Code */ this.emit(Events.QR_RECEIVED, qr); - if (this.options.qrMaxRetries > 0) { qrRetries++; if (qrRetries > this.options.qrMaxRetries) { @@ -197,15 +196,39 @@ class Client extends EventEmitter { await this.destroy(); } } - }; - getQrCode(); - this._qrRefreshInterval = setInterval(getQrCode, this.options.qrRefreshIntervalMs); + }); + + await page.evaluate(function (selectors) { + const qr_container = document.querySelector(selectors.QR_CONTAINER); + window.qrChanged(qr_container.dataset.ref); + + const obs = new MutationObserver((muts) => { + muts.forEach(mut => { + // Listens to qr token change + if (mut.type === 'attributes' &amp;&amp; mut.attributeName === 'data-ref') { + window.qrChanged(mut.target.dataset.ref); + } else + // Listens to retry button, when found, click it + if (mut.type === 'childList') { + const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON); + if (retry_button) retry_button.click(); + } + }); + }); + obs.observe(qr_container.parentElement, { + subtree: true, + childList: true, + attributes: true, + attributeFilter: ['data-ref'], + }); + }, { + QR_CONTAINER, + QR_RETRY_BUTTON + }); // Wait for code scan try { - await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: 0 }); - clearInterval(this._qrRefreshInterval); - this._qrRefreshInterval = undefined; + await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 }); } catch(error) { if ( error.name === 'ProtocolError' &amp;&amp; @@ -218,44 +241,29 @@ class Client extends EventEmitter { throw error; } + } await page.evaluate(ExposeStore, moduleRaid.toString()); - - // Get session tokens - const localStorage = JSON.parse(await page.evaluate(() => { - return JSON.stringify(window.localStorage); - })); - - const session = { - WABrowserId: localStorage.WABrowserId, - WASecretBundle: localStorage.WASecretBundle, - WAToken1: localStorage.WAToken1, - WAToken2: localStorage.WAToken2 - }; + const authEventPayload = await this.authStrategy.getAuthEventPayload(); /** * Emitted when authentication is successful * @event Client#authenticated - * @param {object} session Object containing session information. Can be used to restore the session. - * @param {string} session.WABrowserId - * @param {string} session.WASecretBundle - * @param {string} session.WAToken1 - * @param {string} session.WAToken2 */ - this.emit(Events.AUTHENTICATED, session); + this.emit(Events.AUTHENTICATED, authEventPayload); // Check window.Store Injection await page.waitForFunction('window.Store != undefined'); - const isMD = await page.evaluate(() => { - return window.Store.Features.features.MD_BACKEND; + await page.evaluate(async () => { + // safely unregister service workers + const registrations = await navigator.serviceWorker.getRegistrations(); + for (let registration of registrations) { + registration.unregister(); + } }); - if(isMD) { - throw new Error('Multi-device is not yet supported by whatsapp-web.js. Please check out https://github.com/pedroslopez/whatsapp-web.js/pull/889 to follow the progress.'); - } - //Load util functions (serializers, helper functions) await page.evaluate(LoadUtils); @@ -265,7 +273,7 @@ class Client extends EventEmitter { * @type {ClientInfo} */ this.info = new ClientInfo(this, await page.evaluate(() => { - return window.Store.Conn.serialize(); + return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; })); // Add InterfaceController @@ -429,11 +437,12 @@ class Client extends EventEmitter { if (battery === undefined) return; /** - * Emitted when the battery percentage for the attached device changes + * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. * @event Client#change_battery * @param {object} batteryInfo * @param {number} batteryInfo.battery - The current battery percentage * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) + * @deprecated */ this.emit(Events.BATTERY_CHANGED, { battery, plugged }); }); @@ -452,14 +461,14 @@ class Client extends EventEmitter { * @param {boolean} call.webClientShouldHandle - If Waweb should handle * @param {object} call.participants - Participants */ - const cll = new Call(this,call); + const cll = new Call(this, call); this.emit(Events.INCOMING_CALL, cll); }); - + await page.evaluate(() => { window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); }); - window.Store.Msg.on('change:ack', (msg,ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); }); + window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); }); window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe &amp;&amp; !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); }); @@ -497,9 +506,6 @@ class Client extends EventEmitter { * Closes the client */ async destroy() { - if (this._qrRefreshInterval) { - clearInterval(this._qrRefreshInterval); - } await this.pupBrowser.close(); } @@ -507,9 +513,11 @@ class Client extends EventEmitter { * Logs out the client, closing the current session */ async logout() { - return await this.pupPage.evaluate(() => { + await this.pupPage.evaluate(() => { return window.Store.AppState.logout(); }); + + await this.authStrategy.logout(); } /** @@ -539,7 +547,7 @@ class Client extends EventEmitter { /** * Message options. * @typedef {Object} MessageSendOptions - * @property {boolean} [linkPreview=true] - Show links preview + * @property {boolean} [linkPreview=true] - Show links preview. Has no effect on multi-device accounts. * @property {boolean} [sendAudioAsVoice=false] - Send audio as voice message * @property {boolean} [sendVideoAsGif=false] - Send video as gif * @property {boolean} [sendMediaAsSticker=false] - Send media as a sticker @@ -589,34 +597,36 @@ class Client extends EventEmitter { } else if (content instanceof Location) { internalOptions.location = content; content = ''; - } else if(content instanceof Contact) { + } else if (content instanceof Contact) { internalOptions.contactCard = content.id._serialized; content = ''; - } else if(Array.isArray(content) &amp;&amp; content.length > 0 &amp;&amp; content[0] instanceof Contact) { + } else if (Array.isArray(content) &amp;&amp; content.length > 0 &amp;&amp; content[0] instanceof Contact) { internalOptions.contactCardList = content.map(contact => contact.id._serialized); content = ''; - } else if(content instanceof Buttons){ - if(content.type !== 'chat'){internalOptions.attachment = content.body;} + } else if (content instanceof Buttons) { + if (content.type !== 'chat') { internalOptions.attachment = content.body; } internalOptions.buttons = content; content = ''; - } else if(content instanceof List){ + } else if (content instanceof List) { internalOptions.list = content; content = ''; } if (internalOptions.sendMediaAsSticker &amp;&amp; internalOptions.attachment) { - internalOptions.attachment = - await Util.formatToWebpSticker(internalOptions.attachment, { + internalOptions.attachment = await Util.formatToWebpSticker( + internalOptions.attachment, { name: options.stickerName, author: options.stickerAuthor, categories: options.stickerCategories - }); + }, this.pupPage + ); } const newMessage = await this.pupPage.evaluate(async (chatId, message, options, sendSeen) => { const chatWid = window.Store.WidFactory.createWid(chatId); const chat = await window.Store.Chat.find(chatWid); + if (sendSeen) { window.WWebJS.sendSeen(chatId); } @@ -703,7 +713,7 @@ class Client extends EventEmitter { */ async getInviteInfo(inviteCode) { return await this.pupPage.evaluate(inviteCode => { - return window.Store.Wap.groupInviteInfo(inviteCode); + return window.Store.InviteInfo.sendQueryGroupInvite(inviteCode); }, inviteCode); } @@ -722,25 +732,25 @@ class Client extends EventEmitter { /** * Accepts a private invitation to join a group - * @param {object} inviteV4 Invite V4 Info + * @param {object} inviteInfo Invite V4 Info * @returns {Promise&lt;Object>} */ async acceptGroupV4Invite(inviteInfo) { - if(!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object'; + if (!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object'; if (inviteInfo.inviteCodeExp == 0) throw 'Expired invite code'; - return await this.pupPage.evaluate(async inviteInfo => { - let { groupId, fromId, inviteCode, inviteCodeExp, toId } = inviteInfo; - return await window.Store.Wap.acceptGroupV4Invite(groupId, fromId, inviteCode, String(inviteCodeExp), toId); + return this.pupPage.evaluate(async inviteInfo => { + let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo; + return await window.Store.JoinInviteV4.sendJoinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, fromId); }, inviteInfo); } - + /** * Sets the current user's status message * @param {string} status New status message */ async setStatus(status) { await this.pupPage.evaluate(async status => { - return await window.Store.Wap.sendSetStatus(status); + return await window.Store.StatusUtils.setMyStatus(status); }, status); } @@ -748,11 +758,22 @@ class Client extends EventEmitter { * Sets the current user's display name. * This is the name shown to WhatsApp users that have not added you as a contact beside your number in groups and in your profile. * @param {string} displayName New display name + * @returns {Promise&lt;Boolean>} */ async setDisplayName(displayName) { - await this.pupPage.evaluate(async displayName => { - return await window.Store.Wap.setPushname(displayName); + const couldSet = await this.pupPage.evaluate(async displayName => { + if(!window.Store.Conn.canSetMyPushname()) return false; + + if(window.Store.Features.features.MD_BACKEND) { + // TODO + return false; + } else { + const res = await window.Store.Wap.setPushname(displayName); + return !res.status || res.status === 200; + } }, displayName); + + return couldSet; } /** @@ -771,7 +792,16 @@ class Client extends EventEmitter { */ async sendPresenceAvailable() { return await this.pupPage.evaluate(() => { - return window.Store.Wap.sendPresenceAvailable(); + return window.Store.PresenceUtils.sendPresenceAvailable(); + }); + } + + /** + * Marks the client as unavailable + */ + async sendPresenceUnavailable() { + return await this.pupPage.evaluate(() => { + return window.Store.PresenceUtils.sendPresenceUnavailable(); }); } @@ -878,12 +908,37 @@ class Client extends EventEmitter { */ async getProfilePicUrl(contactId) { const profilePic = await this.pupPage.evaluate((contactId) => { - return window.Store.Wap.profilePicFind(contactId); + const chatWid = window.Store.WidFactory.createWid(contactId); + return window.Store.getProfilePicFull(chatWid); }, contactId); return profilePic ? profilePic.eurl : undefined; } + /** + * Gets the Contact's common groups with you. Returns empty array if you don't have any common group. + * @param {string} contactId the whatsapp user's ID (_serialized format) + * @returns {Promise&lt;WAWebJS.ChatId[]>} + */ + async getCommonGroups(contactId) { + const commonGroups = await this.pupPage.evaluate(async (contactId) => { + const contact = window.Store.Contact.get(contactId); + if (contact.commonGroups) { + return contact.commonGroups.serialize(); + } + const status = await window.Store.findCommonGroups(contact); + if (status) { + return contact.commonGroups.serialize(); + } + return []; + }, contactId); + const chats = []; + for (const group of commonGroups) { + chats.push(group.id); + } + return chats; + } + /** * Force reset of connection state for the client */ @@ -899,10 +954,7 @@ class Client extends EventEmitter { * @returns {Promise&lt;Boolean>} */ async isRegisteredUser(id) { - return await this.pupPage.evaluate(async (id) => { - let result = await window.Store.Wap.queryExist(id); - return result.jid !== undefined; - }, id); + return Boolean(await this.getNumberId(id)); } /** @@ -912,14 +964,15 @@ class Client extends EventEmitter { * @returns {Promise&lt;Object|null>} */ async getNumberId(number) { - if (!number.endsWith('@c.us')) number += '@c.us'; - try { - return await this.pupPage.evaluate(async numberId => { - return window.WWebJS.getNumberId(numberId); - }, number); - } catch(_) { - return null; + if (!number.endsWith('@c.us')) { + number += '@c.us'; } + + return await this.pupPage.evaluate(async number => { + const result = await window.Store.QueryExist(number); + if (!result || result.wid === undefined) return null; + return result.wid; + }, number); } /** @@ -928,14 +981,14 @@ class Client extends EventEmitter { * @returns {Promise&lt;string>} */ async getFormattedNumber(number) { - if(!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net'); - if(!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`; - + if (!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net'); + if (!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`; + return await this.pupPage.evaluate(async numberId => { return window.Store.NumberInfo.formattedPhoneNumber(numberId); }, number); } - + /** * Get the country code of a WhatsApp ID. * @param {string} number Number or ID @@ -948,7 +1001,7 @@ class Client extends EventEmitter { return window.Store.NumberInfo.findCC(numberId); }, number); } - + /** * Create a new group * @param {string} name group title @@ -967,12 +1020,9 @@ class Client extends EventEmitter { } const createRes = await this.pupPage.evaluate(async (name, participantIds) => { - const res = await window.Store.Wap.createGroup(name, participantIds); - console.log(res); - if (!res.status === 200) { - throw 'An error occurred while creating the group!'; - } - + const participantWIDs = participantIds.map(p => window.Store.WidFactory.createWid(p)); + const id = window.Store.genId(); + const res = await window.Store.GroupUtils.sendCreateGroup(name, participantWIDs, undefined, id); return res; }, name, participants); @@ -993,9 +1043,9 @@ class Client extends EventEmitter { async getLabels() { const labels = await this.pupPage.evaluate(async () => { return window.WWebJS.getLabels(); - }); + }); - return labels.map(data => new Label(this , data)); + return labels.map(data => new Label(this, data)); } /** @@ -1006,7 +1056,7 @@ class Client extends EventEmitter { async getLabelById(labelId) { const label = await this.pupPage.evaluate(async (labelId) => { return window.WWebJS.getLabel(labelId); - }, labelId); + }, labelId); return new Label(this, label); } @@ -1016,12 +1066,12 @@ class Client extends EventEmitter { * @param {string} chatId * @returns {Promise&lt;Array&lt;Label>>} */ - async getChatLabels(chatId){ + async getChatLabels(chatId) { const labels = await this.pupPage.evaluate(async (chatId) => { return window.WWebJS.getChatLabels(chatId); }, chatId); - return labels.map(data => new Label(this, data)); + return labels.map(data => new Label(this, data)); } /** @@ -1029,16 +1079,16 @@ class Client extends EventEmitter { * @param {string} labelId * @returns {Promise&lt;Array&lt;Chat>>} */ - async getChatsByLabelId(labelId){ + async getChatsByLabelId(labelId) { const chatIds = await this.pupPage.evaluate(async (labelId) => { const label = window.Store.Label.get(labelId); const labelItems = label.labelItemCollection.models; return labelItems.reduce((result, item) => { - if(item.parentType === 'Chat'){ + if (item.parentType === 'Chat') { result.push(item.parentId); } return result; - },[]); + }, []); }, labelId); return Promise.all(chatIds.map(id => this.getChatById(id))); @@ -1051,7 +1101,7 @@ class Client extends EventEmitter { async getBlockedContacts() { const blockedContacts = await this.pupPage.evaluate(() => { let chatIds = window.Store.Blocklist.models.map(a => a.id._serialized); - return Promise.all(chatIds.map(id => window.WWebJS.getContact(id))); + return Promise.all(chatIds.map(id => window.WWebJS.getContact(id))); }); return blockedContacts.map(contact => ContactFactory.create(this.client, contact)); @@ -1069,7 +1119,7 @@ module.exports = Client;
diff --git a/docs/ClientInfo.html b/docs/ClientInfo.html index 15bb01e..5e1933f 100644 --- a/docs/ClientInfo.html +++ b/docs/ClientInfo.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: ClientInfo + whatsapp-web.js 1.16.0 » Class: ClientInfo @@ -15,7 +15,7 @@ @@ -39,31 +39,26 @@
-
me
-
-
phone
-
-
-
-
platform
-
pushname
-
-
+
pushname
+
+
wid
+
+
@@ -93,15 +88,9 @@

Properties

-

me -  object

-
-
Deprecated
-
Use .wid instead
-

phone  object

-

Information about the phone this client is connected to

+

Information about the phone this client is connected to. Not available in multi-device.

Properties

@@ -188,10 +177,12 @@
+
Deprecated
+

platform  string

-

Platform the phone is running on

+

Platform WhatsApp is running on

pushname @@ -211,6 +202,8 @@

getBatteryStatus() → (object, number, or boolean)

Get current battery percentage and charging status for the attached device

+
Deprecated
+
Returns

object  @@ -238,7 +231,7 @@

diff --git a/docs/Contact.html b/docs/Contact.html index d726f30..30e50f3 100644 --- a/docs/Contact.html +++ b/docs/Contact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Contact + whatsapp-web.js 1.16.0 » Class: Contact @@ -15,7 +15,7 @@ @@ -108,19 +108,22 @@

+
getProfilePicUrl()
+
+
unblock()
@@ -236,6 +239,15 @@
async
+

getCommonGroups() → Promise containing Array of WAWebJS.ChatId

+

Gets the Contact's common groups with you. Returns empty array if you don't have any common group.

+
+
Returns
+
+

Promise containing Array of WAWebJS.ChatId 

+
+
+
async

getCountryCode() → Promise containing string

Returns the contact's countrycode, (1541859685@c.us) => (1)

@@ -281,7 +293,7 @@
diff --git a/docs/GroupChat.html b/docs/GroupChat.html index 9918ae8..62302fe 100644 --- a/docs/GroupChat.html +++ b/docs/GroupChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: GroupChat + whatsapp-web.js 1.16.0 » Class: GroupChat @@ -15,7 +15,7 @@ @@ -645,12 +645,14 @@
async
-

revokeInvite() → Promise

+

revokeInvite() → Promise containing string

Invalidates the current group invite code and generates a new one

Returns
-

Promise 

+

Promise containing string  +

New invite code

+

async
@@ -731,7 +733,7 @@
Chat#sendStateTyping
async
-

setDescription(description) → Promise

+

setDescription(description) → Promise containing boolean

Updates the group description

Parameter

@@ -764,7 +766,9 @@
Returns
-

Promise 

+

Promise containing boolean  +

Returns true if the description was properly updated. This can return false if the user does not have the necessary permissions.

+

async
@@ -850,7 +854,7 @@
async
-

setSubject(subject) → Promise

+

setSubject(subject) → Promise containing boolean

Updates the group subject

Parameter

@@ -883,7 +887,9 @@
Returns
-

Promise 

+

Promise containing boolean  +

Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.

+

async
@@ -921,7 +927,7 @@
diff --git a/docs/GroupNotification.html b/docs/GroupNotification.html index 51b4c41..2090ce5 100644 --- a/docs/GroupNotification.html +++ b/docs/GroupNotification.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: GroupNotification + whatsapp-web.js 1.16.0 » Class: GroupNotification @@ -15,7 +15,7 @@ @@ -233,7 +233,7 @@
diff --git a/docs/InterfaceController.html b/docs/InterfaceController.html index 1d18650..1c6d8cf 100644 --- a/docs/InterfaceController.html +++ b/docs/InterfaceController.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: InterfaceController + whatsapp-web.js 1.16.0 » Class: InterfaceController @@ -15,7 +15,7 @@ @@ -382,7 +382,7 @@
diff --git a/docs/Label.html b/docs/Label.html index 0f95982..e401c74 100644 --- a/docs/Label.html +++ b/docs/Label.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Label + whatsapp-web.js 1.16.0 » Class: Label @@ -15,7 +15,7 @@ @@ -163,7 +163,7 @@
diff --git a/docs/LegacySessionAuth.html b/docs/LegacySessionAuth.html new file mode 100644 index 0000000..414c156 --- /dev/null +++ b/docs/LegacySessionAuth.html @@ -0,0 +1,188 @@ + + + + + + + whatsapp-web.js 1.16.0 » Class: LegacySessionAuth + + + + + + + + +
+
+
+
+ +
+
+
+

new LegacySessionAuth(options)

+
+

Parameters

+ + + + + + + + + + + + + + + + + +
NameTypeOptionalDescription
+

options

+
+

 

+
+

 

+
+

options

+

Values in options have the following properties:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeOptionalDescription
+

restartOnAuthFail

+
+

 

+
+

 

+
+

Restart client with a new session (i.e. use null 'session' var) if authentication fails

+
+

session

+
+

 

+
+

 

+
+

Whatsapp session to restore. If not set, will start a new session

+
+

session.WABrowserId

+
+

 

+
+

 

+
+
+

session.WASecretBundle

+
+

 

+
+

 

+
+
+

session.WAToken1

+
+

 

+
+

 

+
+
+

session.WAToken2

+
+

 

+
+

 

+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/List.html b/docs/List.html index f5aa6f7..857256d 100644 --- a/docs/List.html +++ b/docs/List.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: List + whatsapp-web.js 1.16.0 » Class: List @@ -15,7 +15,7 @@ @@ -256,7 +256,7 @@ Returns: [{'title':'sectionTitle','rows':[{'r
diff --git a/docs/LocalAuth.html b/docs/LocalAuth.html new file mode 100644 index 0000000..41247a5 --- /dev/null +++ b/docs/LocalAuth.html @@ -0,0 +1,135 @@ + + + + + + + whatsapp-web.js 1.16.0 » Class: LocalAuth + + + + + + + + +
+
+
+
+ +
+
+
+

new LocalAuth(options)

+
+

Parameters

+ + + + + + + + + + + + + + + + + +
NameTypeOptionalDescription
+

options

+
+

 

+
+

 

+
+

options

+

Values in options have the following properties:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeOptionalDescription
+

clientId

+
+

 

+
+

 

+
+

Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance

+
+

dataPath

+
+

 

+
+

 

+
+

Change the default path for saving session files, default is: "./.wwebjs_auth/"

+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/Location.html b/docs/Location.html index ece044c..cb67ee1 100644 --- a/docs/Location.html +++ b/docs/Location.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Location + whatsapp-web.js 1.16.0 » Class: Location @@ -15,7 +15,7 @@ @@ -149,7 +149,7 @@
diff --git a/docs/Message.html b/docs/Message.html index f3d9578..ec2acd0 100644 --- a/docs/Message.html +++ b/docs/Message.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Message + whatsapp-web.js 1.16.0 » Class: Message @@ -15,7 +15,7 @@ @@ -54,6 +54,9 @@
deviceType
+
duration
+
+
forwardingScore
@@ -97,13 +100,13 @@
links
+
location
+
+
-
location
-
-
mediaKey
@@ -113,6 +116,9 @@
orderId
+
rawData
+
+
timestamp
@@ -178,6 +184,9 @@
getQuotedMessage()
+
reload()
+
+
reply(content[, chatId][, options])
@@ -227,6 +236,11 @@

String that represents from which device type the message was sent

+

duration +  string

+

Indicates the duration of the message in seconds

+
+

forwardingScore  number

Indicates how many times the message was forwarded.

@@ -313,6 +327,11 @@

Order ID for message type ORDER

+

rawData +  Object

+

Returns message in a raw format

+
+

timestamp  number

Unix timestamp for when the message was created

@@ -496,6 +515,16 @@
async
+

reload() → Promise containing Message

+

Reloads this Message object's data in-place with the latest values from WhatsApp Web. + Note that the Message must still be in the web app cache for this to work, otherwise will return null.

+
+
Returns
+
+

Promise containing Message 

+
+
+
async

reply(content[, chatId][, options]) → Promise containing Message

Sends a message as a reply to this message. If chatId is specified, it will be sent through the specified Chat. If not, it will send the message @@ -580,7 +609,7 @@

diff --git a/docs/MessageMedia.html b/docs/MessageMedia.html index 781b468..08d066e 100644 --- a/docs/MessageMedia.html +++ b/docs/MessageMedia.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: MessageMedia + whatsapp-web.js 1.16.0 » Class: MessageMedia @@ -15,7 +15,7 @@ @@ -343,7 +343,7 @@
diff --git a/docs/NoAuth.html b/docs/NoAuth.html new file mode 100644 index 0000000..6eb911c --- /dev/null +++ b/docs/NoAuth.html @@ -0,0 +1,66 @@ + + + + + + + whatsapp-web.js 1.16.0 » Class: NoAuth + + + + + + + + +
+
+
+
+ +
+
+
+

new NoAuth()

+
+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/Order.html b/docs/Order.html index 96d69db..1b73612 100644 --- a/docs/Order.html +++ b/docs/Order.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Order + whatsapp-web.js 1.16.0 » Class: Order @@ -15,7 +15,7 @@ @@ -102,7 +102,7 @@
diff --git a/docs/PrivateChat.html b/docs/PrivateChat.html index fc3cb17..316c454 100644 --- a/docs/PrivateChat.html +++ b/docs/PrivateChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: PrivateChat + whatsapp-web.js 1.16.0 » Class: PrivateChat @@ -15,7 +15,7 @@ @@ -519,7 +519,7 @@
diff --git a/docs/PrivateContact.html b/docs/PrivateContact.html index 1f3f4c6..4035f05 100644 --- a/docs/PrivateContact.html +++ b/docs/PrivateContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: PrivateContact + whatsapp-web.js 1.16.0 » Class: PrivateContact @@ -15,7 +15,7 @@ @@ -108,19 +108,22 @@
+
getProfilePicUrl()
+
+
unblock()
@@ -262,6 +265,15 @@
async
+

getCommonGroups() → Promise containing Array of WAWebJS.ChatId

+

Gets the Contact's common groups with you. Returns empty array if you don't have any common group.

+
+
Inherited from
+
Contact#getCommonGroups
+
Returns
+
+
+
async

getCountryCode() → Promise containing string

Returns the contact's countrycode, (1541859685@c.us) => (1)

@@ -307,7 +319,7 @@
diff --git a/docs/Product.html b/docs/Product.html index 40a21e4..1a732f1 100644 --- a/docs/Product.html +++ b/docs/Product.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Product + whatsapp-web.js 1.16.0 » Class: Product @@ -15,7 +15,7 @@ @@ -127,7 +127,7 @@
diff --git a/docs/Util.html b/docs/Util.html index c0d09eb..1909096 100644 --- a/docs/Util.html +++ b/docs/Util.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.15.8 » Class: Util + whatsapp-web.js 1.16.0 » Class: Util @@ -15,7 +15,7 @@ @@ -26,7 +26,7 @@
-
- +
+
Message#getInfo()
@@ -2094,10 +2094,6 @@ client.initialize();
-
-
-
-
Message#links
@@ -2108,6 +2104,10 @@ client.initialize();
+
+
+
+
Message#mediaKey
@@ -2128,6 +2128,11 @@ client.initialize();
+
+ Message#react(reaction) +
+
+
Message#reload()
@@ -3149,7 +3154,7 @@ client.initialize();
diff --git a/docs/structures_Base.js.html b/docs/structures_Base.js.html index f64644b..878d9c4 100644 --- a/docs/structures_Base.js.html +++ b/docs/structures_Base.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Base.js + whatsapp-web.js 1.17.0 » Source: structures/Base.js @@ -15,7 +15,7 @@ @@ -60,7 +60,7 @@ module.exports = Base;
diff --git a/docs/structures_BusinessContact.js.html b/docs/structures_BusinessContact.js.html index 5d7565e..539389f 100644 --- a/docs/structures_BusinessContact.js.html +++ b/docs/structures_BusinessContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/BusinessContact.js + whatsapp-web.js 1.17.0 » Source: structures/BusinessContact.js @@ -15,7 +15,7 @@ @@ -59,7 +59,7 @@ module.exports = BusinessContact;
diff --git a/docs/structures_Buttons.js.html b/docs/structures_Buttons.js.html index a3e582e..d98ef04 100644 --- a/docs/structures_Buttons.js.html +++ b/docs/structures_Buttons.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Buttons.js + whatsapp-web.js 1.17.0 » Source: structures/Buttons.js @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@ module.exports = Buttons;
diff --git a/docs/structures_Call.js.html b/docs/structures_Call.js.html index dc33883..4390d3a 100644 --- a/docs/structures_Call.js.html +++ b/docs/structures_Call.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Call.js + whatsapp-web.js 1.17.0 » Source: structures/Call.js @@ -15,7 +15,7 @@ @@ -106,7 +106,7 @@ module.exports = Call;
diff --git a/docs/structures_Chat.js.html b/docs/structures_Chat.js.html index 705255d..92e84b1 100644 --- a/docs/structures_Chat.js.html +++ b/docs/structures_Chat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Chat.js + whatsapp-web.js 1.17.0 » Source: structures/Chat.js @@ -15,7 +15,7 @@ @@ -215,7 +215,7 @@ class Chat extends Base { if (searchOptions &amp;&amp; searchOptions.limit > 0) { while (msgs.length &lt; searchOptions.limit) { const loadedMessages = await window.Store.ConversationMsgs.loadEarlierMsgs(chat); - if (!loadedMessages) break; + if (!loadedMessages || !loadedMessages.length) break; msgs = [...loadedMessages.filter(msgFilter), ...msgs]; } @@ -290,7 +290,7 @@ module.exports = Chat;
diff --git a/docs/structures_ClientInfo.js.html b/docs/structures_ClientInfo.js.html index 5a54abd..a87312b 100644 --- a/docs/structures_ClientInfo.js.html +++ b/docs/structures_ClientInfo.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/ClientInfo.js + whatsapp-web.js 1.17.0 » Source: structures/ClientInfo.js @@ -15,7 +15,7 @@ @@ -109,7 +109,7 @@ module.exports = ClientInfo;
diff --git a/docs/structures_Contact.js.html b/docs/structures_Contact.js.html index 2ba974c..95ac42a 100644 --- a/docs/structures_Contact.js.html +++ b/docs/structures_Contact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Contact.js + whatsapp-web.js 1.17.0 » Source: structures/Contact.js @@ -15,7 +15,7 @@ @@ -245,7 +245,7 @@ module.exports = Contact;
diff --git a/docs/structures_GroupChat.js.html b/docs/structures_GroupChat.js.html index 910aff9..391df25 100644 --- a/docs/structures_GroupChat.js.html +++ b/docs/structures_GroupChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/GroupChat.js + whatsapp-web.js 1.17.0 » Source: structures/GroupChat.js @@ -15,7 +15,7 @@ @@ -178,7 +178,7 @@ class GroupChat extends Chat { this.groupMetadata.desc = description; return true; } - + /** * Updates the group settings to only allow admins to send messages. * @param {boolean} [adminsOnly=true] Enable or disable this option @@ -272,7 +272,7 @@ module.exports = GroupChat;
diff --git a/docs/structures_GroupNotification.js.html b/docs/structures_GroupNotification.js.html index 52970d4..0ade5b5 100644 --- a/docs/structures_GroupNotification.js.html +++ b/docs/structures_GroupNotification.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/GroupNotification.js + whatsapp-web.js 1.17.0 » Source: structures/GroupNotification.js @@ -15,7 +15,7 @@ @@ -143,7 +143,7 @@ module.exports = GroupNotification;
diff --git a/docs/structures_Label.js.html b/docs/structures_Label.js.html index 2dddd36..f8acc76 100644 --- a/docs/structures_Label.js.html +++ b/docs/structures_Label.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Label.js + whatsapp-web.js 1.17.0 » Source: structures/Label.js @@ -15,7 +15,7 @@ @@ -88,7 +88,7 @@ module.exports = Label;
diff --git a/docs/structures_List.js.html b/docs/structures_List.js.html index 5974441..27b0eed 100644 --- a/docs/structures_List.js.html +++ b/docs/structures_List.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/List.js + whatsapp-web.js 1.17.0 » Source: structures/List.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@ module.exports = List;
diff --git a/docs/structures_Location.js.html b/docs/structures_Location.js.html index 0da8069..6026c63 100644 --- a/docs/structures_Location.js.html +++ b/docs/structures_Location.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Location.js + whatsapp-web.js 1.17.0 » Source: structures/Location.js @@ -15,7 +15,7 @@ @@ -71,7 +71,7 @@ module.exports = Location;
diff --git a/docs/structures_Message.js.html b/docs/structures_Message.js.html index d283931..3bb29af 100644 --- a/docs/structures_Message.js.html +++ b/docs/structures_Message.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Message.js + whatsapp-web.js 1.17.0 » Source: structures/Message.js @@ -15,7 +15,7 @@ @@ -366,6 +366,18 @@ class Message extends Base { return this.client.sendMessage(chatId, content, options); } + /** + * React to this message with an emoji + * @param {string} reaction - Emoji to react with. Send an empty string to remove the reaction. + * @return {Promise} + */ + async react(reaction){ + await this.client.pupPage.evaluate(async (messageId, reaction) => { + const msg = await window.Store.Msg.get(messageId); + await window.Store.sendReactionToMsg(msg, reaction); + }, this.id._serialized, reaction); + } + /** * Accept Group V4 Invite * @returns {Promise&lt;Object>} @@ -554,7 +566,7 @@ module.exports = Message;
diff --git a/docs/structures_MessageMedia.js.html b/docs/structures_MessageMedia.js.html index 946108b..a0fbcc9 100644 --- a/docs/structures_MessageMedia.js.html +++ b/docs/structures_MessageMedia.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/MessageMedia.js + whatsapp-web.js 1.17.0 » Source: structures/MessageMedia.js @@ -15,7 +15,7 @@ @@ -142,7 +142,7 @@ module.exports = MessageMedia;
diff --git a/docs/structures_Order.js.html b/docs/structures_Order.js.html index d680f42..6c5a007 100644 --- a/docs/structures_Order.js.html +++ b/docs/structures_Order.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Order.js + whatsapp-web.js 1.17.0 » Source: structures/Order.js @@ -15,7 +15,7 @@ @@ -90,7 +90,7 @@ module.exports = Order;
diff --git a/docs/structures_Payment.js.html b/docs/structures_Payment.js.html index 0fbd039..7494ad8 100644 --- a/docs/structures_Payment.js.html +++ b/docs/structures_Payment.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Payment.js + whatsapp-web.js 1.17.0 » Source: structures/Payment.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@ module.exports = Payment;
diff --git a/docs/structures_PrivateChat.js.html b/docs/structures_PrivateChat.js.html index 756771a..e97628c 100644 --- a/docs/structures_PrivateChat.js.html +++ b/docs/structures_PrivateChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/PrivateChat.js + whatsapp-web.js 1.17.0 » Source: structures/PrivateChat.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@ module.exports = PrivateChat;
diff --git a/docs/structures_PrivateContact.js.html b/docs/structures_PrivateContact.js.html index d62a94e..2cab9ba 100644 --- a/docs/structures_PrivateContact.js.html +++ b/docs/structures_PrivateContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/PrivateContact.js + whatsapp-web.js 1.17.0 » Source: structures/PrivateContact.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@ module.exports = PrivateContact;
diff --git a/docs/structures_Product.js.html b/docs/structures_Product.js.html index ddf184c..4518a45 100644 --- a/docs/structures_Product.js.html +++ b/docs/structures_Product.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/Product.js + whatsapp-web.js 1.17.0 » Source: structures/Product.js @@ -15,7 +15,7 @@ @@ -106,7 +106,7 @@ module.exports = Product;
diff --git a/docs/structures_ProductMetadata.js.html b/docs/structures_ProductMetadata.js.html index 7f6d538..12eb181 100644 --- a/docs/structures_ProductMetadata.js.html +++ b/docs/structures_ProductMetadata.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: structures/ProductMetadata.js + whatsapp-web.js 1.17.0 » Source: structures/ProductMetadata.js @@ -15,7 +15,7 @@ @@ -63,7 +63,7 @@ module.exports = ProductMetadata;
diff --git a/docs/util_Constants.js.html b/docs/util_Constants.js.html index 6896ab5..6d370c2 100644 --- a/docs/util_Constants.js.html +++ b/docs/util_Constants.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: util/Constants.js + whatsapp-web.js 1.17.0 » Source: util/Constants.js @@ -15,7 +15,7 @@ @@ -197,7 +197,7 @@ exports.MessageAck = {
diff --git a/docs/util_InterfaceController.js.html b/docs/util_InterfaceController.js.html index 5649a00..ac19a0b 100644 --- a/docs/util_InterfaceController.js.html +++ b/docs/util_InterfaceController.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: util/InterfaceController.js + whatsapp-web.js 1.17.0 » Source: util/InterfaceController.js @@ -15,7 +15,7 @@ @@ -160,7 +160,7 @@ module.exports = InterfaceController;
diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index 450a295..01e5a75 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.16.7 » Source: util/Util.js + whatsapp-web.js 1.17.0 » Source: util/Util.js @@ -15,7 +15,7 @@ @@ -37,7 +37,6 @@ const { tmpdir } = require('os'); const ffmpeg = require('fluent-ffmpeg'); const webp = require('node-webpmux'); const fs = require('fs').promises; - const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); /** @@ -226,7 +225,7 @@ module.exports = Util; diff --git a/package.json b/package.json index e74ac5d..8332cdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "whatsapp-web.js", - "version": "1.16.7", + "version": "1.17.0", "description": "Library for interacting with the WhatsApp Web API ", "main": "./index.js", "typings": "./index.d.ts",