diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..4a3a1461c7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: [pedroslopez, PurpShell] +ko_fi: pedroslopez +custom: ["paypal.me/psla", "buymeacoff.ee/pedroslopez"] diff --git a/.gitignore b/.gitignore index 79f64679f2..12b53d4ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,7 @@ typings/ # Test sessions *session.json -.wwebjs_auth/ \ No newline at end of file +.wwebjs_auth/ + +# local version cache +.wwebjs_cache/ diff --git a/.npmignore b/.npmignore index a5131c9d9b..0cfaf9d107 100644 --- a/.npmignore +++ b/.npmignore @@ -13,6 +13,7 @@ yarn-error.log* *session.json .wwebjs_auth/ +.wwebjs_cache/ .env tools/ diff --git a/README.md b/README.md index cda4a29e6f..24eba1af5d 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.2224.8](https://img.shields.io/badge/WhatsApp_Web-2.2224.8-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.2346.52](https://img.shields.io/badge/WhatsApp_Web-2.2346.52-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 @@ -18,7 +18,36 @@ It uses Puppeteer to run a real instance of Whatsapp Web to avoid getting blocke The module is now available on npm! `npm i whatsapp-web.js` -Please note that Node v12+ is required. +Please note that Node v18+ is required. + +## QUICK STEPS TO UPGRADE NODE + +### Windows + +#### Manual +Just get the latest LTS from https://nodejs.org + +#### npm +```powershell +sudo npm install -g n +sudo n stable +``` + +#### Choco +```powershell +choco install nodejs-lts +``` + +#### Winget +```powershell +winget install OpenJS.NodeJS.LTS +``` + +### Ubuntu / Debian +```bash +curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - &&\ +sudo apt-get install -y nodejs +``` ## Example usage @@ -63,8 +92,8 @@ For more information on saving and restoring sessions, check out the available [ | Receive media (images/audio/video/documents) | ✅ | | Send contact cards | ✅ | | Send location | ✅ | -| Send buttons | ✅ | -| Send lists | ✅ (business accounts not supported) | +| Send buttons | ❌ | +| Send lists | ❌ [(DEPRECATED)](https://www.youtube.com/watch?v=hv1R1rLeVVE) | | Receive location | ✅ | | Message replies | ✅ | | Join groups by invite | ✅ | @@ -81,6 +110,8 @@ For more information on saving and restoring sessions, check out the available [ | Get profile pictures | ✅ | | Set user status message | ✅ | | React to messages | ✅ | +| Vote in polls | 🔜 | +| Create polls | ✅ | Something missing? Make an issue and let us know! @@ -94,7 +125,7 @@ You can support the maintainer of this project through the links below - [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) +- [Sign up for DigitalOcean](https://m.do.co/c/73f906a36ed4) and get $200 in credit when you sign up (Referral) ## Disclaimer diff --git a/docs/Base.html b/docs/Base.html index 146705d630..c6a0cbb43e 100644 --- a/docs/Base.html +++ b/docs/Base.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Base + whatsapp-web.js 1.23.0 » Class: Base @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@

new Base diff --git a/docs/BaseAuthStrategy.html b/docs/BaseAuthStrategy.html index eb1fd0d5fa..80230baf6b 100644 --- a/docs/BaseAuthStrategy.html +++ b/docs/BaseAuthStrategy.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: BaseAuthStrategy + whatsapp-web.js 1.23.0 » Class: BaseAuthStrategy @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@

new BaseAuthStrategy diff --git a/docs/BusinessContact.html b/docs/BusinessContact.html index 86a7d39824..4c243a3319 100644 --- a/docs/BusinessContact.html +++ b/docs/BusinessContact.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: BusinessContact + whatsapp-web.js 1.23.0 » Class: BusinessContact @@ -15,7 +15,7 @@ @@ -326,7 +326,7 @@

unblock diff --git a/docs/Buttons.html b/docs/Buttons.html index a5269104e5..3baa235623 100644 --- a/docs/Buttons.html +++ b/docs/Buttons.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Buttons + whatsapp-web.js 1.23.0 » Class: Buttons @@ -15,7 +15,7 @@ @@ -234,7 +234,7 @@

Parameter

diff --git a/docs/Call.html b/docs/Call.html index d6ecec6298..b7489d0911 100644 --- a/docs/Call.html +++ b/docs/Call.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Call + whatsapp-web.js 1.23.0 » Class: Call @@ -15,7 +15,7 @@ @@ -78,6 +78,22 @@

Properties

+
+

Method

+
+
+
+
reject()
+
+
+
+
+
+
+
+
+
+

new Call()

@@ -135,6 +151,14 @@

webClientShouldHandle

+

Method

+
+
async
+

reject()

+

Reject the call

+
+
+
@@ -144,7 +168,7 @@

webClientShouldHandle diff --git a/docs/Chat.html b/docs/Chat.html index a1f3a9a9d2..706ea57e82 100644 --- a/docs/Chat.html +++ b/docs/Chat.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Chat + whatsapp-web.js 1.23.0 » Class: Chat @@ -15,7 +15,7 @@ @@ -58,19 +58,22 @@

Properties

isReadOnly
-
muteExpiration
+
lastMessage
-
name
+
muteExpiration
-
pinned
+
name
+
pinned
+
+
timestamp
@@ -89,6 +92,9 @@

Methods

archive()
+
changeLabels(labelIds)
+
+
clearMessages()
@@ -101,13 +107,13 @@

Methods

fetchMessages(searchOptions)
-
getContact()
-
-
+
getContact()
+
+
getLabels()
@@ -123,13 +129,13 @@

Methods

sendMessage(content[, options])
-
sendSeen()
-
-
+
sendSeen()
+
+
sendStateRecording()
@@ -185,6 +191,11 @@

isReadOnly

+

lastMessage +  Message

+

Last message fo chat

+
+

muteExpiration  number

Unix timestamp for when the mute expires

@@ -219,6 +230,43 @@

archive
async
+

changeLabels(labelIds) → Promise containing void

+

Add or remove labels to this Chat

+
+

Parameter

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

labelIds

+
+

Array of (number or string)

+
+

 

+
+
+
+
+
Returns
+
+

Promise containing void 

+
+
+
async

clearMessages() → Promise containing Boolean

Clears all messages from the chat

@@ -271,7 +319,7 @@

Parameters

 

-

Options for searching messages. Right now only limit is supported.

+

Options for searching messages. Right now only limit and fromMe is supported.

Values in searchOptions have the following properties:

@@ -297,6 +345,20 @@

Parameters

The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages.

+ + + + + +
+

fromMe

+
+

Boolean

+
+

Yes

+
+

Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.

+
@@ -483,7 +545,7 @@

unpin diff --git a/docs/Client.html b/docs/Client.html index 599d86a97c..46398b9e61 100644 --- a/docs/Client.html +++ b/docs/Client.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Client + whatsapp-web.js 1.23.0 » Class: Client @@ -15,7 +15,7 @@ @@ -26,7 +26,7 @@

+
message_ack
+
+
message_create
+
message_edit
+
+
+
message_reaction
+
+
message_revoke_everyone
@@ -304,6 +343,34 @@

Parameters

Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used.

+ + +

webVersion

+ + +

 

+ + +

 

+ + +

The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved.

+ + + + +

webVersionCache

+ + +

 

+ + +

 

+ + +

Determines how to retrieve the WhatsApp Web version. Defaults to a local cache (LocalWebCache) that falls back to latest if the requested version is not found.

+ +

authTimeoutMs

@@ -444,6 +511,20 @@

Parameters

Sets bypassing of page's Content-Security-Policy.

+ + +

proxyAuthentication

+ + +

 

+ + +

 

+ + +

Proxy Authentication object.

+ + @@ -470,6 +551,9 @@

Parameters

Client#event:group_update
Client#event:disconnected
Client#event:change_state
+
Client#event:contact_changed
+
Client#event:group_admin_changed
+
Client#event:group_membership_request
@@ -562,6 +646,110 @@

Parameter

async
+

addOrRemoveLabels(labelIds, chatIds) → Promise containing void

+

Change labels in chats

+
+

Parameters

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

labelIds

+
+

Array of (number or string)

+
+

 

+
+
+

chatIds

+
+

Array of string

+
+

 

+
+
+
+
+
Returns
+
+

Promise containing void 

+
+
+
async
+

approveGroupMembershipRequests(groupId, options) → Promise containing Array of MembershipRequestActionResult

+

Approves membership requests if any

+
+

Parameters

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

groupId

+
+

string

+
+

 

+
+

The group ID to get the membership request for

+
+

options

+
+

MembershipRequestActionOptions

+
+

 

+
+

Options for performing a membership request action

+
+
+
+
Returns
+
+

Promise containing Array of MembershipRequestActionResult  +

Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned

+

+
+
+
async

archiveChat() → boolean

Enables and returns the archive state of the Chat

@@ -571,8 +759,8 @@

archiveChatasync

-

createGroup(name, participants) → (Object, string, or Object with string properties)

-

Create a new group

+

createGroup(title, participants, options) → Promise containing (CreateGroupResult or string)

+

Creates a new group

Parameters

@@ -587,7 +775,7 @@

Parameters

@@ -604,13 +792,27 @@

Parameters

participants

+ + + + + + @@ -619,18 +821,19 @@

Parameters

Returns
-

Object  -

createRes

-

-
-
-

string  -

createRes.gid - ID for the group that was just created

+

Promise containing (CreateGroupResult or string)  +

Object with resulting data or an error message as a string

+
+
async
+

deleteProfilePicture() → Promise containing boolean

+

Deletes the current user's profile picture.

+
+
Returns
-

Object with string properties  -

createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user's privacy settings don't allow you to add them to groups.

+

Promise containing boolean  +

Returns true if the picture was properly deleted.

@@ -929,6 +1132,46 @@

Parameter

async
+

getGroupMembershipRequests(groupId) → Promise containing Array of GroupMembershipRequest

+

Gets an array of membership requests

+
+

Parameter

+
-

name

+

title

string

@@ -596,7 +784,7 @@

Parameters

 

-

group title

+

Group title

-

Array of (Contact or string)

+

(string, Contact, Array of (Contact or string), or undefined)

+
+

 

+
+

A single Contact object or an ID as a string or an array of Contact objects or contact IDs to add to the group

+
+

options

+
+

CreateGroupOptions

 

-

an array of Contacts or contact IDs to add to the group

+

An object that handles options for group creation

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

groupId

+
+

string

+
+

 

+
+

The ID of a group to get membership requests for

+
+
+
+
Returns
+
+

Promise containing Array of GroupMembershipRequest  +

An array of membership requests

+

+
+
+
async

getInviteInfo(inviteCode) → Promise containing object

Returns an object with information about the invite code's group

@@ -1251,13 +1494,8 @@

pinChat
async
-

resetState()

-

Force reset of connection state for the client

-
-
-
async
-

searchMessages(query[, options]) → Promise containing Array of Message

-

Searches for messages

+

rejectGroupMembershipRequests(groupId, options) → Promise containing Array of MembershipRequestActionResult

+

Rejects membership requests if any

Parameters

@@ -1272,7 +1510,7 @@

Parameters

@@ -1288,36 +1527,94 @@

Parameters

options

-

query

+

groupId

string

@@ -1281,6 +1519,7 @@

Parameters

 

+

The group ID to get the membership request for

-

Object

+

MembershipRequestActionOptions

-

Yes

+

 

-

Values in options have the following properties:

- - - - - - - - - - - - - - - - +

Options for performing a membership request action

+ + + +
NameTypeOptionalDescription
-

page

-
-

number

-
-

Yes

-
-
+ +
+
Returns
+
+

Promise containing Array of MembershipRequestActionResult  +

Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned

+

+
+
+
async
+

resetState()

+

Force reset of connection state for the client

+
+
+
async
+

searchMessages(query[, options]) → Promise containing Array of Message

+

Searches for messages

+
+

Parameters

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

query

+
+

string

+
+

 

+
+
+

options

+
+

Object

+
+

Yes

+
+

Values in options have the following properties:

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

page

+
+

number

+
+

Yes

+
+

limit

@@ -1390,7 +1687,7 @@

Parameters

content

-

(string, MessageMedia, Location, Contact, Array of Contact, Buttons, or List)

+

(string, MessageMedia, Location, Poll, Contact, Array of Contact, Buttons, or List)

 

@@ -1512,6 +1809,45 @@

Parameter

async
+

setProfilePicture(media) → Promise containing boolean

+

Sets the current user's profile picture.

+
+

Parameter

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

media

+
+

MessageMedia

+
+

 

+
+
+
+
+
Returns
+
+

Promise containing boolean  +

Returns true if the picture was properly updated.

+

+
+
+
async

setStatus(status)

Sets the current user's status message

@@ -1746,10 +2082,10 @@

Parameter

-

disconnected

-

Emitted when the client has been disconnected

+

chat_archived

+

Emitted when a chat is archived/unarchived

-

Parameter

+

Parameters

@@ -1762,16 +2098,41 @@

Parameter

+ + + + + + + + + + + + @@ -1779,8 +2140,8 @@

Parameter

-

group_join

-

Emitted when a user joins the chat via invite link or is added by an admin.

+

chat_removed

+

Emitted when a chat is removed

Parameter

-

reason

+

chat

-

(WAState or "NAVIGATION")

+

Chat

+
+

 

+
+
+

currState

+
+

boolean

+
+

 

+
+
+

prevState

+
+

boolean

 

-

reason that caused the disconnect

@@ -1795,16 +2156,15 @@

Parameter

@@ -1812,8 +2172,84 @@

Parameter

-

group_leave

-

Emitted when a user leaves the chat or is removed by an admin.

+

contact_changed

+

Emitted when a contact or a group participant changes their phone number.

+
+

Parameters

+
-

notification

+

chat

-

GroupNotification

+

Chat

 

-

GroupNotification with more information about the action

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

message

+
+

Message

+
+

 

+
+

Message with more information about the event.

+
+

oldId

+
+

String

+
+

 

+
+

The user's id (an old one) who changed their phone number + and who triggered the notification.

+
+

newId

+
+

String

+
+

 

+
+

The user's new id after the change.

+
+

isContact

+
+

Boolean

+
+

 

+
+

Indicates if a contact or a group participant changed their phone number.

+
+
+
+
+

disconnected

+

Emitted when the client has been disconnected

Parameter

@@ -1828,16 +2264,16 @@

Parameter

@@ -1845,8 +2281,8 @@

Parameter

-

group_update

-

Emitted when group settings are updated, such as subject, description or picture.

+

group_admin_changed

+

Emitted when a current user is promoted to an admin or demoted to a regular user.

Parameter

-

notification

+

reason

-

GroupNotification

+

(WAState or "NAVIGATION")

 

-

GroupNotification with more information about the action

+

reason that caused the disconnect

@@ -1878,10 +2314,10 @@

Parameter

-

incoming_call

-

Emitted when a call is received

+

group_join

+

Emitted when a user joins the chat via invite link or is added by an admin.

-

Parameters

+

Parameter

@@ -1894,39 +2330,227 @@

Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-

call

+

notification

-

object

+

GroupNotification

 

-

Values in call have the following properties:

- - - - - - - - - - - - - - - +

GroupNotification with more information about the action

+ + + +
NameTypeOptionalDescription
-

id

-
-

number

-
-

 

-
-

Call id

-
+ +
+
+

group_leave

+

Emitted when a user leaves the chat or is removed by an admin.

+
+

Parameter

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

notification

+
+

GroupNotification

+
+

 

+
+

GroupNotification with more information about the action

+
+
+
+
+

group_membership_request

+

Emitted when some user requested to join the group + that has the membership approval mode turned on

+
+

Parameters

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

notification

+
+

GroupNotification

+
+

 

+
+

GroupNotification with more information about the action

+

Values in notification have the following properties:

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

chatId

+
+

string

+
+

 

+
+

The group ID the request was made for

+
+

author

+
+

string

+
+

 

+
+

The user ID that made a request

+
+

timestamp

+
+

number

+
+

 

+
+

The timestamp the request was made at

+
+
+
+
+
+

group_update

+

Emitted when group settings are updated, such as subject, description or picture.

+
+

Parameter

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

notification

+
+

GroupNotification

+
+

 

+
+

GroupNotification with more information about the action

+
+
+
+
+

incoming_call

+

Emitted when a call is received

+
+

Parameters

+ + + + + + + + + + + + + + + @@ -519,7 +583,7 @@

unpin diff --git a/docs/PrivateContact.html b/docs/PrivateContact.html index 35d012f692..a1c8aeb4b0 100644 --- a/docs/PrivateContact.html +++ b/docs/PrivateContact.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: PrivateContact + whatsapp-web.js 1.23.0 » Class: PrivateContact @@ -15,7 +15,7 @@ @@ -319,7 +319,7 @@

unblock diff --git a/docs/Product.html b/docs/Product.html index 746dd69623..510c8aa6a6 100644 --- a/docs/Product.html +++ b/docs/Product.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Product + whatsapp-web.js 1.23.0 » Class: Product @@ -15,7 +15,7 @@ @@ -127,7 +127,7 @@

thumbnailUrl diff --git a/docs/Reaction.html b/docs/Reaction.html new file mode 100644 index 0000000000..510a7220e3 --- /dev/null +++ b/docs/Reaction.html @@ -0,0 +1,159 @@ + + + + + + + whatsapp-web.js 1.23.0 » Class: Reaction + + + + + + + + +
+
+
+
+ +
+
+

Properties

+
+
+
+
ack
+
+
+
id
+
+
+
msgId
+
+
+
+
+
+
+
orphan
+
+
+
orphanReason
+
+
+
reaction
+
+
+
+
+
+
+
read
+
+
+
senderId
+
+
+
timestamp
+
+
+
+
+
+
+
+
+

new Reaction()

+
+
Extends
+
Base
+
+
+
+

Properties

+
+

ack +  nullable number

+

ACK

+
+
+

id +  object

+

Reaction ID

+
+
+

msgId +  object

+

Message ID

+
+
+

orphan +  number

+

Orphan

+
+
+

orphanReason +  nullable string

+

Orphan reason

+
+
+

reaction +  string

+

Reaction

+
+
+

read +  boolean

+

Read

+
+
+

senderId +  string

+

Sender ID

+
+
+

timestamp +  number

+

Unix timestamp for when the reaction was created

+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/RemoteAuth.html b/docs/RemoteAuth.html new file mode 100644 index 0000000000..d55ea497f4 --- /dev/null +++ b/docs/RemoteAuth.html @@ -0,0 +1,163 @@ + + + + + + + whatsapp-web.js 1.23.0 » Class: RemoteAuth + + + + + + + + +
+
+
+
+ +
+
+
+

new RemoteAuth(options)

+
+

Parameters

+

NameTypeOptionalDescription
+

call

+
+

object

+
+

 

+
+

Values in call have the following properties:

+ + + + + + + + + + + + + + + @@ -485,6 +631,17 @@

getContactasync +

getGroupMembershipRequests() → Promise containing Array of GroupMembershipRequest

+

Gets an array of membership requests

+
+
Returns
+
+

Promise containing Array of GroupMembershipRequest  +

An array of membership requests

+

+
+
+
async

getInviteCode() → Promise containing string

Gets the invite code for a specific group

@@ -608,7 +765,47 @@

Parameter

async
-

removeParticipants(participantIds) → Promise containing Object

+

rejectGroupMembershipRequests(options) → Promise containing Array of MembershipRequestActionResult

+

Rejects membership requests if any

+
+

Parameter

+

NameTypeOptionalDescription
+

id

+
+

number

+
+

 

+
+

Call id

+
@@ -2181,6 +2805,237 @@

Parameter

+

message_edit

+

Emitted when messages are edited

+
+

Parameters

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

message

+
+

Message

+
+

 

+
+
+

newBody

+
+

string

+
+

 

+
+
+

prevBody

+
+

string

+
+

 

+
+
+
+
+
+

message_reaction

+

Emitted when a reaction is sent, received, updated or removed

+
+

Parameters

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

reaction

+
+

object

+
+

 

+
+

Values in reaction have the following properties:

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

id

+
+

object

+
+

 

+
+

Reaction id

+
+

orphan

+
+

number

+
+

 

+
+

Orphan

+
+

orphanReason

+
+

string

+
+

 

+
+

Orphan reason

+

Value can be null.

+
+

timestamp

+
+

number

+
+

 

+
+

Timestamp

+
+

reaction

+
+

string

+
+

 

+
+

Reaction

+
+

read

+
+

boolean

+
+

 

+
+

Read

+
+

msgId

+
+

object

+
+

 

+
+

Parent message id

+
+

senderId

+
+

string

+
+

 

+
+

Sender id

+
+

ack

+
+

number

+
+

 

+
+

Ack

+

Value can be null.

+
+
+
+
+

message_revoke_everyone

Emitted when a message is deleted for everyone in the chat.

@@ -2310,7 +3165,7 @@

ready

diff --git a/docs/Client.js.html b/docs/Client.js.html index 1e5b887f0b..0fe7276791 100644 --- a/docs/Client.js.html +++ b/docs/Client.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: Client.js + whatsapp-web.js 1.23.0 » Source: Client.js @@ -15,7 +15,7 @@ @@ -41,7 +41,8 @@

Source: Client.js

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 WebCacheFactory = require('./webCache/WebCacheFactory'); +const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures'); const LegacySessionAuth = require('./authStrategies/LegacySessionAuth'); const NoAuth = require('./authStrategies/NoAuth'); @@ -50,6 +51,8 @@

Source: Client.js

* @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 {string} options.webVersion - The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved. + * @param {object} options.webVersionCache - Determines how to retrieve the WhatsApp Web version. Defaults to a local cache (LocalWebCache) that falls back to latest if the requested version is not found. * @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 @@ -60,6 +63,7 @@

Source: Client.js

* @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 {object} options.proxyAuthentication - Proxy Authentication object. * * @fires Client#qr * @fires Client#authenticated @@ -76,6 +80,9 @@

Source: Client.js

* @fires Client#group_update * @fires Client#disconnected * @fires Client#change_state + * @fires Client#contact_changed + * @fires Client#group_admin_changed + * @fires Client#group_membership_request */ class Client extends EventEmitter { constructor(options = {}) { @@ -131,6 +138,10 @@

Source: Client.js

browser = await puppeteer.launch({...puppeteerOpts, args: browserArgs}); page = (await browser.pages())[0]; } + + if (this.options.proxyAuthentication !== undefined) { + await page.authenticate(this.options.proxyAuthentication); + } await page.setUserAgent(this.options.userAgent); if (this.options.bypassCSP) await page.setBypassCSP(true); @@ -139,6 +150,7 @@

Source: Client.js

this.pupPage = page; await this.authStrategy.afterBrowserInitialized(); + await this.initWebVersionCache(); await page.goto(WhatsWebURL, { waitUntil: 'load', @@ -146,7 +158,53 @@

Source: Client.js

referer: 'https://whatsapp.com/' }); - 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"]'; + await page.evaluate(`function getElementByXpath(path) { + return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; + }`); + + let lastPercent = null, + lastPercentMessage = null; + + await page.exposeFunction('loadingScreen', async (percent, message) => { + if (lastPercent !== percent || lastPercentMessage !== message) { + this.emit(Events.LOADING_SCREEN, percent, message); + lastPercent = percent; + lastPercentMessage = message; + } + }); + + await page.evaluate( + async function (selectors) { + var observer = new MutationObserver(function () { + let progressBar = window.getElementByXpath( + selectors.PROGRESS + ); + let progressMessage = window.getElementByXpath( + selectors.PROGRESS_MESSAGE + ); + + if (progressBar) { + window.loadingScreen( + progressBar.value, + progressMessage.innerText + ); + } + }); + + observer.observe(document, { + attributes: true, + childList: true, + characterData: true, + subtree: true, + }); + }, + { + PROGRESS: '//*[@id=\'app\']/div/div/div[2]/progress', + PROGRESS_MESSAGE: '//*[@id=\'app\']/div/div/div[3]', + } + ); + + const INTRO_IMG_SELECTOR = '[data-icon=\'search\']'; const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas'; // Checks which selector appears first @@ -212,9 +270,9 @@

Source: Client.js

// 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') { + else if (mut.type === 'childList') { const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON); if (retry_button) retry_button.click(); } @@ -249,6 +307,50 @@

Source: Client.js

} + await page.evaluate(() => { + /** + * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version. + * @param {string} lOperand The left operand for the WWeb version string to compare with + * @param {string} operator The comparison operator + * @param {string} rOperand The right operand for the WWeb version string to compare with + * @returns {boolean} Boolean value that indicates the result of the comparison + */ + window.compareWwebVersions = (lOperand, operator, rOperand) => { + if (!['>', '>=', '<', '<=', '='].includes(operator)) { + throw new class _ extends Error { + constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } + }('Invalid comparison operator is provided'); + + } + if (typeof lOperand !== 'string' || typeof rOperand !== 'string') { + throw new class _ extends Error { + constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } + }('A non-string WWeb version type is provided'); + } + + lOperand = lOperand.replace(/-beta$/, ''); + rOperand = rOperand.replace(/-beta$/, ''); + + while (lOperand.length !== rOperand.length) { + lOperand.length > rOperand.length + ? rOperand = rOperand.concat('0') + : lOperand = lOperand.concat('0'); + } + + lOperand = Number(lOperand.replace(/\./g, '')); + rOperand = Number(rOperand.replace(/\./g, '')); + + return ( + operator === '>' ? lOperand > rOperand : + operator === '>=' ? lOperand >= rOperand : + operator === '<' ? lOperand < rOperand : + operator === '<=' ? lOperand <= rOperand : + operator === '=' ? lOperand === rOperand : + false + ); + }; + }); + await page.evaluate(ExposeStore, moduleRaid.toString()); const authEventPayload = await this.authStrategy.getAuthEventPayload(); @@ -288,7 +390,7 @@

Source: Client.js

await page.exposeFunction('onAddMessageEvent', msg => { if (msg.type === 'gp2') { const notification = new GroupNotification(this, msg); - if (msg.subtype === 'add' || msg.subtype === 'invite') { + if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) { /** * Emitted when a user joins the chat via invite link or is added by an admin. * @event Client#group_join @@ -302,6 +404,24 @@

Source: Client.js

* @param {GroupNotification} notification GroupNotification with more information about the action */ this.emit(Events.GROUP_LEAVE, notification); + } else if (msg.subtype === 'promote' || msg.subtype === 'demote') { + /** + * Emitted when a current user is promoted to an admin or demoted to a regular user. + * @event Client#group_admin_changed + * @param {GroupNotification} notification GroupNotification with more information about the action + */ + this.emit(Events.GROUP_ADMIN_CHANGED, notification); + } else if (msg.subtype === 'created_membership_requests') { + /** + * Emitted when some user requested to join the group + * that has the membership approval mode turned on + * @event Client#group_membership_request + * @param {GroupNotification} notification GroupNotification with more information about the action + * @param {string} notification.chatId The group ID the request was made for + * @param {string} notification.author The user ID that made a request + * @param {number} notification.timestamp The timestamp the request was made at + */ + this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification); } else { /** * Emitted when group settings are updated, such as subject, description or picture. @@ -361,6 +481,36 @@

Source: Client.js

last_message = msg; } + /** + * The event notification that is received when one of + * the group participants changes their phone number. + */ + const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify'; + + /** + * The event notification that is received when one of + * the contacts changes their phone number. + */ + const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number'; + + if (isParticipant || isContact) { + /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */ + const message = new Message(this, msg); + + const newId = isParticipant ? msg.recipients[0] : msg.to; + const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId); + + /** + * Emitted when a contact or a group participant changes their phone number. + * @event Client#contact_changed + * @param {Message} message Message with more information about the event. + * @param {String} oldId The user's id (an old one) who changed their phone number + * and who triggered the notification. + * @param {String} newId The user's new id after the change. + * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number. + */ + this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact); + } }); await page.exposeFunction('onRemoveMessageEvent', (msg) => { @@ -392,6 +542,15 @@

Source: Client.js

}); + await page.exposeFunction('onChatUnreadCountEvent', async (data) =>{ + const chat = await this.getChatById(data.id); + + /** + * Emitted when the chat unread count changes + */ + this.emit(Events.UNREAD_COUNT, chat); + }); + await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => { const message = new Message(this, msg); @@ -404,7 +563,7 @@

Source: Client.js

this.emit(Events.MEDIA_UPLOADED, message); }); - await page.exposeFunction('onAppStateChangedEvent', (state) => { + await page.exposeFunction('onAppStateChangedEvent', async (state) => { /** * Emitted when the connection state changes @@ -431,6 +590,7 @@

Source: Client.js

* @event Client#disconnected * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect */ + await this.authStrategy.disconnect(); this.emit(Events.DISCONNECTED, state); this.destroy(); } @@ -470,15 +630,78 @@

Source: Client.js

this.emit(Events.INCOMING_CALL, cll); }); + await page.exposeFunction('onReaction', (reactions) => { + for (const reaction of reactions) { + /** + * Emitted when a reaction is sent, received, updated or removed + * @event Client#message_reaction + * @param {object} reaction + * @param {object} reaction.id - Reaction id + * @param {number} reaction.orphan - Orphan + * @param {?string} reaction.orphanReason - Orphan reason + * @param {number} reaction.timestamp - Timestamp + * @param {string} reaction.reaction - Reaction + * @param {boolean} reaction.read - Read + * @param {object} reaction.msgId - Parent message id + * @param {string} reaction.senderId - Sender id + * @param {?number} reaction.ack - Ack + */ + + this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction)); + } + }); + + await page.exposeFunction('onRemoveChatEvent', async (chat) => { + const _chat = await this.getChatById(chat.id); + + /** + * Emitted when a chat is removed + * @event Client#chat_removed + * @param {Chat} chat + */ + this.emit(Events.CHAT_REMOVED, _chat); + }); + + await page.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => { + const _chat = await this.getChatById(chat.id); + + /** + * Emitted when a chat is archived/unarchived + * @event Client#chat_archived + * @param {Chat} chat + * @param {boolean} currState + * @param {boolean} prevState + */ + this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState); + }); + + await page.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => { + + if(msg.type === 'revoked'){ + return; + } + /** + * Emitted when messages are edited + * @event Client#message_edit + * @param {Message} message + * @param {string} newBody + * @param {string} prevBody + */ + this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); + }); + 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: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.Msg.on('change:body', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); }); window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); }); window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); }); window.Store.Call.on('add', (call) => { window.onIncomingCall(call); }); + window.Store.Chat.on('remove', async (chat) => { window.onRemoveChatEvent(await window.WWebJS.getChatModel(chat)); }); + window.Store.Chat.on('change:archive', async (chat, currState, prevState) => { window.onArchiveChatEvent(await window.WWebJS.getChatModel(chat), currState, prevState); }); window.Store.Msg.on('add', (msg) => { if (msg.isNewMsg) { if(msg.type === 'ciphertext') { @@ -489,6 +712,23 @@

Source: Client.js

} } }); + window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);}); + + { + const module = window.Store.createOrUpdateReactionsModule; + const ogMethod = module.createOrUpdateReactions; + module.createOrUpdateReactions = ((...args) => { + window.onReaction(args[0].map(reaction => { + const msgKey = window.Store.MsgKey.fromString(reaction.msgKey); + const parentMsgKey = window.Store.MsgKey.fromString(reaction.parentMsgKey); + const timestamp = reaction.timestamp / 1000; + + return {...reaction, msgKey, parentMsgKey, timestamp }; + })); + + return ogMethod(...args); + }).bind(module); + } }); /** @@ -496,22 +736,54 @@

Source: Client.js

* @event Client#ready */ this.emit(Events.READY); + this.authStrategy.afterAuthReady(); // Disconnect when navigating away when in PAIRING state (detect logout) this.pupPage.on('framenavigated', async () => { const appState = await this.getState(); if(!appState || appState === WAState.PAIRING) { + await this.authStrategy.disconnect(); this.emit(Events.DISCONNECTED, 'NAVIGATION'); await this.destroy(); } }); } + async initWebVersionCache() { + const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; + const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions); + + const requestedVersion = this.options.webVersion; + const versionContent = await webCache.resolve(requestedVersion); + + if(versionContent) { + await this.pupPage.setRequestInterception(true); + this.pupPage.on('request', async (req) => { + if(req.url() === WhatsWebURL) { + req.respond({ + status: 200, + contentType: 'text/html', + body: versionContent + }); + } else { + req.continue(); + } + }); + } else { + this.pupPage.on('response', async (res) => { + if(res.ok() && res.url() === WhatsWebURL) { + await webCache.persist(await res.text()); + } + }); + } + } + /** * Closes the client */ async destroy() { await this.pupBrowser.close(); + await this.authStrategy.destroy(); } /** @@ -521,7 +793,14 @@

Source: Client.js

await this.pupPage.evaluate(() => { return window.Store.AppState.logout(); }); - + await this.pupBrowser.close(); + + let maxDelay = 0; + while (this.pupBrowser.isConnected() && (maxDelay < 10)) { // waits a maximum of 1 second before calling the AuthStrategy + await new Promise(resolve => setTimeout(resolve, 100)); + maxDelay++; + } + await this.authStrategy.logout(); } @@ -553,10 +832,11 @@

Source: Client.js

* Message options. * @typedef {Object} MessageSendOptions * @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} [sendAudioAsVoice=false] - Send audio as voice message with a generated waveform * @property {boolean} [sendVideoAsGif=false] - Send video as gif * @property {boolean} [sendMediaAsSticker=false] - Send media as a sticker * @property {boolean} [sendMediaAsDocument=false] - Send media as a document + * @property {boolean} [isViewOnce=false] - Send photo/video as a view once message * @property {boolean} [parseVCards=true] - Automatically parse vCards and send them as contacts * @property {string} [caption] - Image or video caption * @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to) @@ -567,16 +847,20 @@

Source: Client.js

* @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null. * @property {MessageMedia} [media] - Media to be sent */ - + /** * Send a message to a specific chatId * @param {string} chatId - * @param {string|MessageMedia|Location|Contact|Array<Contact>|Buttons|List} content + * @param {string|MessageMedia|Location|Poll|Contact|Array<Contact>|Buttons|List} content * @param {MessageSendOptions} [options] - Options used when sending the message * * @returns {Promise<Message>} Message that was just sent */ async sendMessage(chatId, content, options = {}) { + if (options.mentions && options.mentions.some(possiblyContact => possiblyContact instanceof Contact)) { + console.warn('Mentions with an array of Contact are now deprecated. See more at https://github.com/pedroslopez/whatsapp-web.js/pull/2166.'); + options.mentions = options.mentions.map(a => a.id._serialized); + } let internalOptions = { linkPreview: options.linkPreview === false ? undefined : true, sendAudioAsVoice: options.sendAudioAsVoice, @@ -586,7 +870,7 @@

Source: Client.js

caption: options.caption, quotedMessageId: options.quotedMessageId, parseVCards: options.parseVCards === false ? false : true, - mentionedJidList: Array.isArray(options.mentions) ? options.mentions.map(contact => contact.id._serialized) : [], + mentionedJidList: Array.isArray(options.mentions) ? options.mentions : [], extraOptions: options.extra }; @@ -594,14 +878,19 @@

Source: Client.js

if (content instanceof MessageMedia) { internalOptions.attachment = content; + internalOptions.isViewOnce = options.isViewOnce, content = ''; } else if (options.media instanceof MessageMedia) { internalOptions.attachment = options.media; internalOptions.caption = content; + internalOptions.isViewOnce = options.isViewOnce, content = ''; } else if (content instanceof Location) { internalOptions.location = content; content = ''; + } else if (content instanceof Poll) { + internalOptions.poll = content; + content = ''; } else if (content instanceof Contact) { internalOptions.contactCard = content.id._serialized; content = ''; @@ -616,7 +905,7 @@

Source: Client.js

internalOptions.list = content; content = ''; } - + if (internalOptions.sendMediaAsSticker && internalOptions.attachment) { internalOptions.attachment = await Util.formatToWebpSticker( internalOptions.attachment, { @@ -642,7 +931,7 @@

Source: Client.js

return new Message(this, newMessage); } - + /** * Searches for messages * @param {string} query @@ -710,6 +999,24 @@

Source: Client.js

return ContactFactory.create(this, contact); } + + async getMessageById(messageId) { + const msg = await this.pupPage.evaluate(async messageId => { + let msg = window.Store.Msg.get(messageId); + if(msg) return window.WWebJS.getMessageModel(msg); + + const params = messageId.split('_'); + if(params.length !== 3) throw new Error('Invalid serialized message id specified'); + + let messagesObject = await window.Store.Msg.getMessagesById([messageId]); + if (messagesObject && messagesObject.messages.length) msg = messagesObject.messages[0]; + + if(msg) return window.WWebJS.getMessageModel(msg); + }, messageId); + + if(msg) return new Message(this, msg); + return null; + } /** * Returns an object with information about the invite code's group @@ -718,7 +1025,7 @@

Source: Client.js

*/ async getInviteInfo(inviteCode) { return await this.pupPage.evaluate(inviteCode => { - return window.Store.InviteInfo.sendQueryGroupInvite(inviteCode); + return window.Store.GroupInvite.queryGroupInvite(inviteCode); }, inviteCode); } @@ -728,11 +1035,11 @@

Source: Client.js

* @returns {Promise<string>} Id of the joined Chat */ async acceptInvite(inviteCode) { - const chatId = await this.pupPage.evaluate(async inviteCode => { - return await window.Store.Invite.sendJoinGroupViaInvite(inviteCode); + const res = await this.pupPage.evaluate(async inviteCode => { + return await window.Store.GroupInvite.joinGroupViaInvite(inviteCode); }, inviteCode); - return chatId._serialized; + return res.gid._serialized; } /** @@ -745,7 +1052,8 @@

Source: Client.js

if (inviteInfo.inviteCodeExp == 0) throw 'Expired invite code'; return this.pupPage.evaluate(async inviteInfo => { let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo; - return await window.Store.JoinInviteV4.sendJoinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, fromId); + let userWid = window.Store.WidFactory.createWid(fromId); + return await window.Store.GroupInviteV4.joinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, userWid); }, inviteInfo); } @@ -881,7 +1189,7 @@

Source: Client.js

unmuteDate = unmuteDate ? unmuteDate.getTime() / 1000 : -1; await this.pupPage.evaluate(async (chatId, timestamp) => { let chat = await window.Store.Chat.get(chatId); - await chat.mute.mute(timestamp, !0); + await chat.mute.mute({expiration: timestamp, sendDevice:!0}); }, chatId, unmuteDate || -1); } @@ -933,7 +1241,13 @@

Source: Client.js

*/ async getCommonGroups(contactId) { const commonGroups = await this.pupPage.evaluate(async (contactId) => { - const contact = window.Store.Contact.get(contactId); + let contact = window.Store.Contact.get(contactId); + if (!contact) { + const wid = window.Store.WidFactory.createUserWid(contactId); + const chatConstructor = window.Store.Contact.getModelsArray().find(c=>!c.isGroup).constructor; + contact = new chatConstructor({id: wid}); + } + if (contact.commonGroups) { return contact.commonGroups.serialize(); } @@ -1015,37 +1329,116 @@

Source: Client.js

} /** - * Create a new group - * @param {string} name group title - * @param {Array<Contact|string>} participants an array of Contacts or contact IDs to add to the group - * @returns {Object} createRes - * @returns {string} createRes.gid - ID for the group that was just created - * @returns {Object.<string,string>} createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user's privacy settings don't allow you to add them to groups. + * An object that represents the result for a participant added to a group + * @typedef {Object} ParticipantResult + * @property {number} statusCode The status code of the result + * @property {string} message The result message + * @property {boolean} isGroupCreator Indicates if the participant is a group creator + * @property {boolean} isInviteV4Sent Indicates if the inviteV4 was sent to the participant */ - async createGroup(name, participants) { - if (!Array.isArray(participants) || participants.length == 0) { - throw 'You need to add at least one other participant to the group'; - } - if (participants.every(c => c instanceof Contact)) { - participants = participants.map(c => c.id._serialized); - } + /** + * An object that handles the result for {@link createGroup} method + * @typedef {Object} CreateGroupResult + * @property {string} title A group title + * @property {Object} gid An object that handles the newly created group ID + * @property {string} gid.server + * @property {string} gid.user + * @property {string} gid._serialized + * @property {Object.<string, ParticipantResult>} participants An object that handles the result value for each added to the group participant + */ - const createRes = await this.pupPage.evaluate(async (name, participantIds) => { - const participantWIDs = participantIds.map(p => window.Store.WidFactory.createWid(p)); - const id = window.Store.MsgKey.newId(); - const res = await window.Store.GroupUtils.sendCreateGroup(name, participantWIDs, undefined, id); - return res; - }, name, participants); - - const missingParticipants = createRes.participants.reduce(((missing, c) => { - const id = Object.keys(c)[0]; - const statusCode = c[id].code; - if (statusCode != 200) return Object.assign(missing, { [id]: statusCode }); - return missing; - }), {}); - - return { gid: createRes.gid, missingParticipants }; + /** + * An object that handles options for group creation + * @typedef {Object} CreateGroupOptions + * @property {number} [messageTimer = 0] The number of seconds for the messages to disappear in the group (0 by default, won't take an effect if the group is been creating with myself only) + * @property {string|undefined} parentGroupId The ID of a parent community group to link the newly created group with (won't take an effect if the group is been creating with myself only) + * @property {boolean} [autoSendInviteV4 = true] If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default) + * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default) + */ + + /** + * Creates a new group + * @param {string} title Group title + * @param {string|Contact|Array<Contact|string>|undefined} participants A single Contact object or an ID as a string or an array of Contact objects or contact IDs to add to the group + * @param {CreateGroupOptions} options An object that handles options for group creation + * @returns {Promise<CreateGroupResult|string>} Object with resulting data or an error message as a string + */ + async createGroup(title, participants = [], options = {}) { + !Array.isArray(participants) && (participants = [participants]); + participants.map(p => (p instanceof Contact) ? p.id._serialized : p); + + return await this.pupPage.evaluate(async (title, participants, options) => { + const { messageTimer = 0, parentGroupId, autoSendInviteV4 = true, comment = '' } = options; + const participantData = {}, participantWids = [], failedParticipants = []; + let createGroupResult, parentGroupWid; + + const addParticipantResultCodes = { + default: 'An unknown error occupied while adding a participant', + 200: 'The participant was added successfully', + 403: 'The participant can be added by sending private invitation only', + 404: 'The phone number is not registered on WhatsApp' + }; + + for (const participant of participants) { + const pWid = window.Store.WidFactory.createWid(participant); + if ((await window.Store.QueryExist(pWid))?.wid) participantWids.push(pWid); + else failedParticipants.push(participant); + } + + parentGroupId && (parentGroupWid = window.Store.WidFactory.createWid(parentGroupId)); + + try { + createGroupResult = await window.Store.GroupUtils.createGroup( + title, + participantWids, + messageTimer, + parentGroupWid + ); + } catch (err) { + return 'CreateGroupError: An unknown error occupied while creating a group'; + } + + for (const participant of createGroupResult.participants) { + let isInviteV4Sent = false; + const participantId = participant.wid._serialized; + const statusCode = participant.error ?? 200; + + if (autoSendInviteV4 && statusCode === 403) { + window.Store.ContactCollection.gadd(participant.wid, { silent: true }); + const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage( + await window.Store.Chat.find(participant.wid), + createGroupResult.wid._serialized, + createGroupResult.subject, + participant.invite_code, + participant.invite_code_exp, + comment, + await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid) + ); + isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') + ? addParticipantResult === 'OK' + : addParticipantResult.messageSendResult === 'OK'; + } + + participantData[participantId] = { + statusCode: statusCode, + message: addParticipantResultCodes[statusCode] || addParticipantResultCodes.default, + isGroupCreator: participant.type === 'superadmin', + isInviteV4Sent: isInviteV4Sent + }; + } + + for (const f of failedParticipants) { + participantData[f] = { + statusCode: 404, + message: addParticipantResultCodes[404], + isGroupCreator: false, + isInviteV4Sent: false + }; + } + + return { title: title, gid: createGroupResult.wid, participants: participantData }; + }, title, participants, options); } /** @@ -1118,6 +1511,123 @@

Source: Client.js

return blockedContacts.map(contact => ContactFactory.create(this.client, contact)); } + + /** + * Sets the current user's profile picture. + * @param {MessageMedia} media + * @returns {Promise<boolean>} Returns true if the picture was properly updated. + */ + async setProfilePicture(media) { + const success = await this.pupPage.evaluate((chatid, media) => { + return window.WWebJS.setPicture(chatid, media); + }, this.info.wid._serialized, media); + + return success; + } + + /** + * Deletes the current user's profile picture. + * @returns {Promise<boolean>} Returns true if the picture was properly deleted. + */ + async deleteProfilePicture() { + const success = await this.pupPage.evaluate((chatid) => { + return window.WWebJS.deletePicture(chatid); + }, this.info.wid._serialized); + + return success; + } + + /** + * Change labels in chats + * @param {Array<number|string>} labelIds + * @param {Array<string>} chatIds + * @returns {Promise<void>} + */ + async addOrRemoveLabels(labelIds, chatIds) { + + return this.pupPage.evaluate(async (labelIds, chatIds) => { + if (['smba', 'smbi'].indexOf(window.Store.Conn.platform) === -1) { + throw '[LT01] Only Whatsapp business'; + } + const labels = window.WWebJS.getLabels().filter(e => labelIds.find(l => l == e.id) !== undefined); + const chats = window.Store.Chat.filter(e => chatIds.includes(e.id._serialized)); + + let actions = labels.map(label => ({id: label.id, type: 'add'})); + + chats.forEach(chat => { + (chat.labels || []).forEach(n => { + if (!actions.find(e => e.id == n)) { + actions.push({id: n, type: 'remove'}); + } + }); + }); + + return await window.Store.Label.addOrRemoveLabels(actions, chats); + }, labelIds, chatIds); + } + + /** + * An object that handles the information about the group membership request + * @typedef {Object} GroupMembershipRequest + * @property {Object} id The wid of a user who requests to enter the group + * @property {Object} addedBy The wid of a user who created that request + * @property {Object|null} parentGroupId The wid of a community parent group to which the current group is linked + * @property {string} requestMethod The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin + * @property {number} t The timestamp the request was created at + */ + + /** + * Gets an array of membership requests + * @param {string} groupId The ID of a group to get membership requests for + * @returns {Promise<Array<GroupMembershipRequest>>} An array of membership requests + */ + async getGroupMembershipRequests(groupId) { + return await this.pupPage.evaluate(async (gropId) => { + const groupWid = window.Store.WidFactory.createWid(gropId); + return await window.Store.MembershipRequestUtils.getMembershipApprovalRequests(groupWid); + }, groupId); + } + + /** + * An object that handles the result for membership request action + * @typedef {Object} MembershipRequestActionResult + * @property {string} requesterId User ID whos membership request was approved/rejected + * @property {number|undefined} error An error code that occurred during the operation for the participant + * @property {string} message A message with a result of membership request action + */ + + /** + * An object that handles options for {@link approveGroupMembershipRequests} and {@link rejectGroupMembershipRequests} methods + * @typedef {Object} MembershipRequestActionOptions + * @property {Array<string>|string|null} requesterIds User ID/s who requested to join the group, if no value is provided, the method will search for all membership requests for that group + * @property {Array<number>|number|null} sleep The number of milliseconds to wait before performing an operation for the next requester. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500] + */ + + /** + * Approves membership requests if any + * @param {string} groupId The group ID to get the membership request for + * @param {MembershipRequestActionOptions} options Options for performing a membership request action + * @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned + */ + async approveGroupMembershipRequests(groupId, options = {}) { + return await this.pupPage.evaluate(async (groupId, options) => { + const { requesterIds = null, sleep = [250, 500] } = options; + return await window.WWebJS.membershipRequestAction(groupId, 'Approve', requesterIds, sleep); + }, groupId, options); + } + + /** + * Rejects membership requests if any + * @param {string} groupId The group ID to get the membership request for + * @param {MembershipRequestActionOptions} options Options for performing a membership request action + * @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned + */ + async rejectGroupMembershipRequests(groupId, options = {}) { + return await this.pupPage.evaluate(async (groupId, options) => { + const { requesterIds = null, sleep = [250, 500] } = options; + return await window.WWebJS.membershipRequestAction(groupId, 'Reject', requesterIds, sleep); + }, groupId, options); + } } module.exports = Client; @@ -1131,7 +1641,7 @@

Source: Client.js

diff --git a/docs/ClientInfo.html b/docs/ClientInfo.html index 0bb31af40a..015f210d86 100644 --- a/docs/ClientInfo.html +++ b/docs/ClientInfo.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: ClientInfo + whatsapp-web.js 1.23.0 » Class: ClientInfo @@ -15,7 +15,7 @@ @@ -242,7 +242,7 @@

getBatteryStatus diff --git a/docs/Contact.html b/docs/Contact.html index 5988ce31f1..5a9f658598 100644 --- a/docs/Contact.html +++ b/docs/Contact.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Contact + whatsapp-web.js 1.23.0 » Class: Contact @@ -15,7 +15,7 @@ @@ -293,7 +293,7 @@

unblock diff --git a/docs/GroupChat.html b/docs/GroupChat.html index a54f8dfe8f..37e2024f2e 100644 --- a/docs/GroupChat.html +++ b/docs/GroupChat.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: GroupChat + whatsapp-web.js 1.23.0 » Class: GroupChat @@ -15,7 +15,7 @@ @@ -64,19 +64,22 @@

Properties

isReadOnly
-
muteExpiration
+
lastMessage
-
name
+
muteExpiration
-
owner
+
name
+
owner
+
+
participants
@@ -98,12 +101,18 @@

Methods

+
sendStateRecording()
+
+
sendStateTyping()
@@ -178,6 +196,9 @@

Methods

setMessagesAdminsOnly([adminsOnly])
+
setPicture(media)
+
+
setSubject(subject)
@@ -250,6 +271,13 @@

isReadOnlyChat#isReadOnly

+

lastMessage +  unknown

+

Last message fo chat

+
+
Inherited from
+
Chat#lastMessage
+

muteExpiration  unknown

Unix timestamp for when the mute expires

@@ -299,10 +327,10 @@

unreadCountasync

-

addParticipants(participantIds) → Promise containing Object

+

addParticipants(participantIds, options) → Promise containing (Object with AddParticipantsResult properties or string)

Adds a list of participants by ID to the group

-

Parameter

+

Parameters

@@ -318,12 +346,66 @@

Parameter

participantIds

+ + + + + + + + + + +
-

Array of string

+

(string or Array of string)

+
+

 

+
+
+

options

+
+

AddParticipnatsOptions

+
+

 

+
+

An object thay handles options for adding participants

+
+
+
+
Returns
+
+

Promise containing (Object with AddParticipantsResult properties or string)  +

Returns an object with the resulting data or an error message as a string

+

+
+
+
async
+

approveGroupMembershipRequests(options) → Promise containing Array of MembershipRequestActionResult

+

Approves membership requests if any

+
+

Parameter

+ + + + + + + + + + + + + @@ -332,7 +414,9 @@

Parameter

Returns
-

Promise containing Object 

+

Promise containing Array of MembershipRequestActionResult  +

Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned

+

async
@@ -343,6 +427,43 @@

archiveChat#archive
async
+

changeLabels(labelIds) → Promise containing void

+

Add or remove labels to this Chat

+
+

Parameter

+

NameTypeOptionalDescription
+

options

+
+

MembershipRequestActionOptions

 

+

Options for performing a membership request action

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

labelIds

+
+

 

+
+

 

+
+
+
+
+
Inherited from
+
Chat#changeLabels
+
Returns
+
+
+
async

clearMessages() → Promise containing Boolean

Clears all messages from the chat

@@ -372,6 +493,17 @@

delete<

async
+

deletePicture() → Promise containing boolean

+

Deletes the group's picture.

+
+
Returns
+
+

Promise containing boolean  +

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

+

+
+
+
async

demoteParticipants(participantIds) → Promise containing {status: number}

Demotes participants by IDs to regular users

@@ -436,7 +568,7 @@

Parameters

 

-

Options for searching messages. Right now only limit is supported.

+

Options for searching messages. Right now only limit and fromMe is supported.

Values in searchOptions have the following properties:

@@ -462,6 +594,20 @@

Parameters

The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages.

+ + + + + +
+

fromMe

+
+

 

+
+

Yes

+
+

Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.

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

options

+
+

MembershipRequestActionOptions

+
+

 

+
+

Options for performing a membership request action

+
+ +
+
Returns
+
+

Promise containing Array of MembershipRequestActionResult  +

Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned

+

+
+
+
async
+

removeParticipants(participantIds) → Promise containing {status: number}

Removes a list of participants by ID to the group

Parameter

@@ -641,7 +838,7 @@

Parameter

Returns
-

Promise containing Object 

+

Promise containing {status: number} 

async
@@ -854,6 +1051,45 @@

Parameter

async
+

setPicture(media) → Promise containing boolean

+

Sets the group's picture.

+
+

Parameter

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

media

+
+

MessageMedia

+
+

 

+
+
+
+
+
Returns
+
+

Promise containing boolean  +

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

+

+
+
+
async

setSubject(subject) → Promise containing boolean

Updates the group subject

@@ -927,7 +1163,7 @@

unpin diff --git a/docs/GroupNotification.html b/docs/GroupNotification.html index 64aeb49bcc..a66ec9356e 100644 --- a/docs/GroupNotification.html +++ b/docs/GroupNotification.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: GroupNotification + whatsapp-web.js 1.23.0 » Class: GroupNotification @@ -15,7 +15,7 @@ @@ -233,7 +233,7 @@

Parameters

diff --git a/docs/InterfaceController.html b/docs/InterfaceController.html index 5776e72744..262c1227a9 100644 --- a/docs/InterfaceController.html +++ b/docs/InterfaceController.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: InterfaceController + whatsapp-web.js 1.23.0 » Class: InterfaceController @@ -15,7 +15,7 @@ @@ -382,7 +382,7 @@

Parameter

diff --git a/docs/Label.html b/docs/Label.html index e678cd74dd..8b3219a335 100644 --- a/docs/Label.html +++ b/docs/Label.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Label + whatsapp-web.js 1.23.0 » Class: Label @@ -15,7 +15,7 @@ @@ -163,7 +163,7 @@

getChats diff --git a/docs/LegacySessionAuth.html b/docs/LegacySessionAuth.html index 5455ef32ac..4cabecabf3 100644 --- a/docs/LegacySessionAuth.html +++ b/docs/LegacySessionAuth.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: LegacySessionAuth + whatsapp-web.js 1.23.0 » Class: LegacySessionAuth @@ -15,7 +15,7 @@ @@ -173,7 +173,7 @@

Parameters

diff --git a/docs/List.html b/docs/List.html index 953b5dcb88..9135bdf07c 100644 --- a/docs/List.html +++ b/docs/List.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: List + whatsapp-web.js 1.23.0 » Class: List @@ -15,7 +15,7 @@ @@ -256,7 +256,7 @@

Parameter

diff --git a/docs/LocalAuth.html b/docs/LocalAuth.html index 96ec06c6ab..81f524ff85 100644 --- a/docs/LocalAuth.html +++ b/docs/LocalAuth.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: LocalAuth + whatsapp-web.js 1.23.0 » Class: LocalAuth @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@

Parameters

diff --git a/docs/LocalWebCache.html b/docs/LocalWebCache.html new file mode 100644 index 0000000000..bd21a4d3bb --- /dev/null +++ b/docs/LocalWebCache.html @@ -0,0 +1,135 @@ + + + + + + + whatsapp-web.js 1.23.0 » Class: LocalWebCache + + + + + + + + +
+
+
+
+ +
+
+
+

new LocalWebCache(options)

+
+

Parameters

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

options

+
+

 

+
+

 

+
+

options

+

Values in options have the following properties:

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

path

+
+

 

+
+

 

+
+

Path to the directory where cached versions are saved, default is: "./.wwebjs_cache/"

+
+

strict

+
+

 

+
+

 

+
+

If true, will throw an error if the requested version can't be fetched. If false, will resolve to the latest version.

+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/Location.html b/docs/Location.html index a41e833983..588d5764c1 100644 --- a/docs/Location.html +++ b/docs/Location.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Location + whatsapp-web.js 1.23.0 » Class: Location @@ -15,7 +15,7 @@ @@ -26,7 +26,7 @@

-

new Location(latitude, longitude, description)

+

new Location(latitude, longitude[, options])

Parameters

@@ -103,16 +112,16 @@

Parameters

@@ -124,9 +133,14 @@

Parameters

Properties

+

address +  (string or undefined)

+

Location address

+
+

description -  nullable string

-

Name for the location

+  (string or undefined) +

Location full description

latitude @@ -139,6 +153,16 @@

longitude +

name +  (string or undefined)

+

Name for the location

+
+
+

url +  (string or undefined)

+

URL address to be shown within a location message

+
+

@@ -149,7 +173,7 @@

longitude diff --git a/docs/Message.html b/docs/Message.html index 95fb491fe0..64420eccc4 100644 --- a/docs/Message.html +++ b/docs/Message.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Message + whatsapp-web.js 1.23.0 » Class: Message @@ -15,7 +15,7 @@ @@ -26,7 +26,7 @@

@@ -419,8 +433,59 @@

downloadMedia
async
+

edit(content[, options]) → Promise containing nullable Message

+

Edits the current message.

+
+

Parameters

+

-

description

+

options

 

-

 

+

Yes

-

Value can be null.

+

Location send options

-

If true and the message is sent by the current user, will delete it for everyone in the chat.

+

If true and the message is sent by the current user or the user is an admin, will delete it for everyone in the chat.

Value can be null.

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

content

+
+

string

+
+

 

+
+
+

options

+
+

MessageEditOptions

+
+

Yes

+
+

Options used when editing the message

+
+
+
+
Returns
+
+

Promise containing nullable Message 

+
+
+
async

forward(chat) → Promise

-

Forwards this message to another chat

+

Forwards this message to another chat (that you chatted before, otherwise it will fail)

Parameter

@@ -518,6 +583,15 @@

getQuotedMessage
async
+

getReactions() → Promise containing Array of ReactionList

+

Gets the reactions associated with the given message

+
+
Returns
+
+

Promise containing Array of ReactionList 

+
+
+
async

react(reaction) → Promise

React to this message with an emoji

@@ -650,7 +724,7 @@

unstar<
diff --git a/docs/MessageMedia.html b/docs/MessageMedia.html index c2857b5074..4b58ccbe24 100644 --- a/docs/MessageMedia.html +++ b/docs/MessageMedia.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: MessageMedia + whatsapp-web.js 1.23.0 » Class: MessageMedia @@ -15,7 +15,7 @@ @@ -26,7 +26,7 @@

-

new MessageMedia(mimetype, data, filename)

+

new MessageMedia(mimetype, data, filename, filesize)

Parameters

@@ -135,7 +136,22 @@

Parameters

 

+ + + + + + @@ -155,7 +171,12 @@

data

filename  nullable string

-

Name of the file (for documents)

+

Document file name. Value can be null

+
+
+

filesize +  nullable number

+

Document file size in bytes. Value can be null

mimetype @@ -343,7 +364,7 @@

Parameters

diff --git a/docs/NoAuth.html b/docs/NoAuth.html index 8ff57ef73c..36f7a61379 100644 --- a/docs/NoAuth.html +++ b/docs/NoAuth.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: NoAuth + whatsapp-web.js 1.23.0 » Class: NoAuth @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@

new NoAuth diff --git a/docs/Order.html b/docs/Order.html index dde65eebbb..ac40b6c026 100644 --- a/docs/Order.html +++ b/docs/Order.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Order + whatsapp-web.js 1.23.0 » Class: Order @@ -15,7 +15,7 @@ @@ -102,7 +102,7 @@

total diff --git a/docs/Poll.html b/docs/Poll.html new file mode 100644 index 0000000000..ba996d6dee --- /dev/null +++ b/docs/Poll.html @@ -0,0 +1,163 @@ + + + + + + + whatsapp-web.js 1.23.0 » Class: Poll + + + + + + + + +
+
+
+
+ +
+
+

Properties

+
+
+
+
options
+
+
+
+
+
+
+
pollName
+
+
+
+
+
+
+
pollOptions
+
+
+
+
+
+
+
+
+

new Poll(pollName, pollOptions, options)

+
+

Parameters

+

-

Document file name

+

Document file name. Value can be null

+

Value can be null.

+
+

filesize

+
+

 

+
+

 

+
+

Document file size in bytes. Value can be null

Value can be null.

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

pollName

+
+

 

+
+

 

+
+
+

pollOptions

+
+

 

+
+

 

+
+
+

options

+
+

 

+
+

 

+
+
+
+
+
+
+
+

Properties

+
+

options +  PollSendOptions

+

The send options for the poll

+
+
+

pollName +  string

+

The name of the poll

+
+
+

pollOptions +  Array of {name: string, localId: number}

+

The array of poll options

+
+
+
+
+ + + + + +
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/PrivateChat.html b/docs/PrivateChat.html index 3203df9015..d708426bc9 100644 --- a/docs/PrivateChat.html +++ b/docs/PrivateChat.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: PrivateChat + whatsapp-web.js 1.23.0 » Class: PrivateChat @@ -15,7 +15,7 @@ @@ -58,19 +58,22 @@

Properties

isReadOnly
-
muteExpiration
+
lastMessage
-
name
+
muteExpiration
-
pinned
+
name
+
pinned
+
+
timestamp
@@ -89,6 +92,9 @@

Methods

archive()
+
changeLabels(labelIds)
+
+
clearMessages()
@@ -101,13 +107,13 @@

Methods

fetchMessages(searchOptions)
-
getContact()
-
-
+
getContact()
+
+
getLabels()
@@ -123,13 +129,13 @@

Methods

sendMessage(content[, options])
-
sendSeen()
-
-
+
sendSeen()
+
+
sendStateRecording()
@@ -195,6 +201,13 @@

isReadOnlyChat#isReadOnly

+

lastMessage +  unknown

+

Last message fo chat

+
+
Inherited from
+
Chat#lastMessage
+

muteExpiration  unknown

Unix timestamp for when the mute expires

@@ -241,6 +254,43 @@

archiveChat#archive
async
+

changeLabels(labelIds) → Promise containing void

+

Add or remove labels to this Chat

+
+

Parameter

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

labelIds

+
+

 

+
+

 

+
+
+
+
+
Inherited from
+
Chat#changeLabels
+
Returns
+
+
+
async

clearMessages() → Promise containing Boolean

Clears all messages from the chat

@@ -295,7 +345,7 @@

Parameters

 

-

Options for searching messages. Right now only limit is supported.

+

Options for searching messages. Right now only limit and fromMe is supported.

Values in searchOptions have the following properties:

@@ -321,6 +371,20 @@

Parameters

The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages.

+ + + + + +
+

fromMe

+
+

 

+
+

Yes

+
+

Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.

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

options

+
+

 

+
+

 

+
+

options

+

Values in options have the following properties:

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

store

+
+

 

+
+

 

+
+

Remote database store instance

+
+

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/"

+
+

backupSyncIntervalMs

+
+

 

+
+

 

+
+

Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute}

+
+
+
+
+
+ +
+
+ + + + + +
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/RemoteWebCache.html b/docs/RemoteWebCache.html new file mode 100644 index 0000000000..b13924890d --- /dev/null +++ b/docs/RemoteWebCache.html @@ -0,0 +1,135 @@ + + + + + + + whatsapp-web.js 1.23.0 » Class: RemoteWebCache + + + + + + + + +
+
+
+
+ +
+
+
+

new RemoteWebCache(options)

+
+

Parameters

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

options

+
+

 

+
+

 

+
+

options

+

Values in options have the following properties:

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

remotePath

+
+

 

+
+

 

+
+

Endpoint that should be used to fetch the version index. Use {version} as a placeholder for the version number.

+
+

strict

+
+

 

+
+

 

+
+

If true, will throw an error if the requested version can't be fetched. If false, will resolve to the latest version. Defaults to false.

+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/Util.html b/docs/Util.html index b64b60624f..9208ce1fca 100644 --- a/docs/Util.html +++ b/docs/Util.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Class: Util + whatsapp-web.js 1.23.0 » Class: Util @@ -15,7 +15,7 @@ @@ -243,7 +243,7 @@

Parameter

diff --git a/docs/WebCache.html b/docs/WebCache.html new file mode 100644 index 0000000000..3acfa732b8 --- /dev/null +++ b/docs/WebCache.html @@ -0,0 +1,65 @@ + + + + + + + whatsapp-web.js 1.23.0 » Class: WebCache + + + + + + + + +
+
+
+
+ +
+
+
+

new WebCache()

+
+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/authStrategies_BaseAuthStrategy.js.html b/docs/authStrategies_BaseAuthStrategy.js.html index 2bbcd7ffb4..b978fd0b55 100644 --- a/docs/authStrategies_BaseAuthStrategy.js.html +++ b/docs/authStrategies_BaseAuthStrategy.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: authStrategies/BaseAuthStrategy.js + whatsapp-web.js 1.23.0 » Source: authStrategies/BaseAuthStrategy.js @@ -15,7 +15,7 @@ @@ -49,6 +49,9 @@

Source: authStrategies/BaseAuthStrategy.js

}; } async getAuthEventPayload() {} + async afterAuthReady() {} + async disconnect() {} + async destroy() {} async logout() {} } @@ -62,7 +65,7 @@

Source: authStrategies/BaseAuthStrategy.js

diff --git a/docs/authStrategies_LegacySessionAuth.js.html b/docs/authStrategies_LegacySessionAuth.js.html index 6167ba9a39..28c10c421b 100644 --- a/docs/authStrategies_LegacySessionAuth.js.html +++ b/docs/authStrategies_LegacySessionAuth.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: authStrategies/LegacySessionAuth.js + whatsapp-web.js 1.23.0 » Source: authStrategies/LegacySessionAuth.js @@ -15,7 +15,7 @@ @@ -111,7 +111,7 @@

Source: authStrategies/LegacySessionAuth.js

diff --git a/docs/authStrategies_LocalAuth.js.html b/docs/authStrategies_LocalAuth.js.html index ac7832cf9c..ab861875b0 100644 --- a/docs/authStrategies_LocalAuth.js.html +++ b/docs/authStrategies_LocalAuth.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: authStrategies/LocalAuth.js + whatsapp-web.js 1.23.0 » Source: authStrategies/LocalAuth.js @@ -15,7 +15,7 @@ @@ -75,13 +75,14 @@

Source: authStrategies/LocalAuth.js

async logout() { if (this.userDataDir) { - return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true }); + return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true, force: true }); } } } -module.exports = LocalAuth; +module.exports = LocalAuth; + @@ -91,7 +92,7 @@

Source: authStrategies/LocalAuth.js

diff --git a/docs/authStrategies_NoAuth.js.html b/docs/authStrategies_NoAuth.js.html index 50349b0f93..60e4ecbba5 100644 --- a/docs/authStrategies_NoAuth.js.html +++ b/docs/authStrategies_NoAuth.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: authStrategies/NoAuth.js + whatsapp-web.js 1.23.0 » Source: authStrategies/NoAuth.js @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@

Source: authStrategies/NoAuth.js

diff --git a/docs/authStrategies_RemoteAuth.js.html b/docs/authStrategies_RemoteAuth.js.html new file mode 100644 index 0000000000..09bd8b2577 --- /dev/null +++ b/docs/authStrategies_RemoteAuth.js.html @@ -0,0 +1,258 @@ + + + + + + + whatsapp-web.js 1.23.0 » Source: authStrategies/RemoteAuth.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+/* Require Optional Dependencies */
+try {
+    var fs = require('fs-extra');
+    var unzipper = require('unzipper');
+    var archiver = require('archiver');
+} catch {
+    fs = undefined;
+    unzipper = undefined;
+    archiver = undefined;
+}
+
+const path = require('path');
+const { Events } = require('./../util/Constants');
+const BaseAuthStrategy = require('./BaseAuthStrategy');
+
+/**
+ * Remote-based authentication
+ * @param {object} options - options
+ * @param {object} options.store - Remote database store instance
+ * @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/" 
+ * @param {number} options.backupSyncIntervalMs - Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute}
+ */
+class RemoteAuth extends BaseAuthStrategy {
+    constructor({ clientId, dataPath, store, backupSyncIntervalMs } = {}) {
+        if (!fs &amp;&amp; !unzipper &amp;&amp; !archiver) throw new Error('Optional Dependencies [fs-extra, unzipper, archiver] are required to use RemoteAuth. Make sure to run npm install correctly and remove the --no-optional flag');
+        super();
+
+        const idRegex = /^[-_\w]+$/i;
+        if (clientId &amp;&amp; !idRegex.test(clientId)) {
+            throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.');
+        }
+        if (!backupSyncIntervalMs || backupSyncIntervalMs &lt; 60000) {
+            throw new Error('Invalid backupSyncIntervalMs. Accepts values starting from 60000ms {1 minute}.');
+        }
+        if(!store) throw new Error('Remote database store is required.');
+
+        this.store = store;
+        this.clientId = clientId;
+        this.backupSyncIntervalMs = backupSyncIntervalMs;
+        this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
+        this.tempDir = `${this.dataPath}/wwebjs_temp_session_${this.clientId}`;
+        this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files &amp; Dirs in WWebJS to restore session */
+    }
+
+    async beforeBrowserInitialized() {
+        const puppeteerOpts = this.client.options.puppeteer;
+        const sessionDirName = this.clientId ? `RemoteAuth-${this.clientId}` : 'RemoteAuth';
+        const dirPath = path.join(this.dataPath, sessionDirName);
+
+        if (puppeteerOpts.userDataDir &amp;&amp; puppeteerOpts.userDataDir !== dirPath) {
+            throw new Error('RemoteAuth is not compatible with a user-supplied userDataDir.');
+        }
+
+        this.userDataDir = dirPath;
+        this.sessionName = sessionDirName;
+
+        await this.extractRemoteSession();
+
+        this.client.options.puppeteer = {
+            ...puppeteerOpts,
+            userDataDir: dirPath
+        };
+    }
+
+    async logout() {
+        await this.disconnect();
+    }
+
+    async destroy() {
+        clearInterval(this.backupSync);
+    }
+
+    async disconnect() {
+        await this.deleteRemoteSession();
+
+        let pathExists = await this.isValidPath(this.userDataDir);
+        if (pathExists) {
+            await fs.promises.rm(this.userDataDir, {
+                recursive: true,
+                force: true
+            }).catch(() => {});
+        }
+        clearInterval(this.backupSync);
+    }
+
+    async afterAuthReady() {
+        const sessionExists = await this.store.sessionExists({session: this.sessionName});
+        if(!sessionExists) {
+            await this.delay(60000); /* Initial delay sync required for session to be stable enough to recover */
+            await this.storeRemoteSession({emit: true});
+        }
+        var self = this;
+        this.backupSync = setInterval(async function () {
+            await self.storeRemoteSession();
+        }, this.backupSyncIntervalMs);
+    }
+
+    async storeRemoteSession(options) {
+        /* Compress &amp; Store Session */
+        const pathExists = await this.isValidPath(this.userDataDir);
+        if (pathExists) {
+            await this.compressSession();
+            await this.store.save({session: this.sessionName});
+            await fs.promises.unlink(`${this.sessionName}.zip`);
+            await fs.promises.rm(`${this.tempDir}`, {
+                recursive: true,
+                force: true
+            }).catch(() => {});
+            if(options &amp;&amp; options.emit) this.client.emit(Events.REMOTE_SESSION_SAVED);
+        }
+    }
+
+    async extractRemoteSession() {
+        const pathExists = await this.isValidPath(this.userDataDir);
+        const compressedSessionPath = `${this.sessionName}.zip`;
+        const sessionExists = await this.store.sessionExists({session: this.sessionName});
+        if (pathExists) {
+            await fs.promises.rm(this.userDataDir, {
+                recursive: true,
+                force: true
+            }).catch(() => {});
+        }
+        if (sessionExists) {
+            await this.store.extract({session: this.sessionName, path: compressedSessionPath});
+            await this.unCompressSession(compressedSessionPath);
+        } else {
+            fs.mkdirSync(this.userDataDir, { recursive: true });
+        }
+    }
+
+    async deleteRemoteSession() {
+        const sessionExists = await this.store.sessionExists({session: this.sessionName});
+        if (sessionExists) await this.store.delete({session: this.sessionName});
+    }
+
+    async compressSession() {
+        const archive = archiver('zip');
+        const stream = fs.createWriteStream(`${this.sessionName}.zip`);
+
+        await fs.copy(this.userDataDir, this.tempDir).catch(() => {});
+        await this.deleteMetadata();
+        return new Promise((resolve, reject) => {
+            archive
+                .directory(this.tempDir, false)
+                .on('error', err => reject(err))
+                .pipe(stream);
+
+            stream.on('close', () => resolve());
+            archive.finalize();
+        });
+    }
+
+    async unCompressSession(compressedSessionPath) {
+        var stream = fs.createReadStream(compressedSessionPath);
+        await new Promise((resolve, reject) => {
+            stream.pipe(unzipper.Extract({
+                path: this.userDataDir
+            }))
+                .on('error', err => reject(err))
+                .on('finish', () => resolve());
+        });
+        await fs.promises.unlink(compressedSessionPath);
+    }
+
+    async deleteMetadata() {
+        const sessionDirs = [this.tempDir, path.join(this.tempDir, 'Default')];
+        for (const dir of sessionDirs) {
+            const sessionFiles = await fs.promises.readdir(dir);
+            for (const element of sessionFiles) {
+                if (!this.requiredDirs.includes(element)) {
+                    const dirElement = path.join(dir, element);
+                    const stats = await fs.promises.lstat(dirElement);
+    
+                    if (stats.isDirectory()) {
+                        await fs.promises.rm(dirElement, {
+                            recursive: true,
+                            force: true
+                        }).catch(() => {});
+                    } else {
+                        await fs.promises.unlink(dirElement).catch(() => {});
+                    }
+                }
+            }
+        }
+    }
+
+    async isValidPath(path) {
+        try {
+            await fs.promises.access(path);
+            return true;
+        } catch {
+            return false;
+        }
+    }
+
+    async delay(ms) {
+        return new Promise(resolve => setTimeout(resolve, ms));
+    }
+}
+
+module.exports = RemoteAuth;
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/global.html b/docs/global.html index d20b79b05f..ef801936da 100644 --- a/docs/global.html +++ b/docs/global.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Globals + whatsapp-web.js 1.23.0 » Globals @@ -15,7 +15,7 @@ @@ -68,35 +68,80 @@

Abstract types

@@ -220,6 +265,32 @@

Properties

+

CHAT_REMOVED

+
+

 

+
+

 

+
+
+

CHAT_ARCHIVED

+
+

 

+
+

 

+
+

MESSAGE_RECEIVED

@@ -285,6 +356,45 @@

Properties

+

MESSAGE_EDIT

+
+

 

+
+

 

+
+
+

UNREAD_COUNT

+
+

 

+
+

 

+
+
+

MESSAGE_REACTION

+
+

 

+
+

 

+
+

MEDIA_UPLOADED

@@ -298,6 +408,19 @@

Properties

+

CONTACT_CHANGED

+
+

 

+
+

 

+
+

GROUP_JOIN

@@ -324,6 +447,32 @@

Properties

+

GROUP_ADMIN_CHANGED

+
+

 

+
+

 

+
+
+

GROUP_MEMBERSHIP_REQUEST

+
+

 

+
+

 

+
+

GROUP_UPDATE

@@ -350,6 +499,19 @@

Properties

+

LOADING_SCREEN

+
+

 

+
+

 

+
+

DISCONNECTED

@@ -402,6 +564,19 @@

Properties

+

REMOTE_SESSION_SAVED

+
+

 

+
+

 

+
+
@@ -1115,6 +1290,19 @@

Properties

+

POLL_CREATION

+
+

 

+
+

 

+
+
@@ -1360,9 +1548,9 @@

Properties

Abstract types

-

ButtonSpec +

AddParticipantsResult  Object

-

Button spec used in Buttons constructor

+

An object that handles the result for addParticipants method

Properties

@@ -1377,21 +1565,21 @@

Properties

+ + + + + + @@ -1408,9 +1610,9 @@

Properties

-

ContactId +

AddParticipnatsOptions  Object

-

ID that represents a contact

+

An object that handles options for adding participants

Properties

-

id

+

code

-

string

+

number

-

Yes

+

 

-

Custom ID to set on the button. A random one will be generated if one is not passed.

+

The code of the result

-

body

+

message

string

@@ -1400,7 +1588,21 @@

Properties

 

-

The text to show on the button.

+

The result message

+
+

isInviteV4Sent

+
+

boolean

+
+

 

+
+

Indicates if the inviteV4 was sent to the partitipant

@@ -1425,41 +1627,47 @@

Properties

@@ -1467,8 +1675,9 @@

Properties

-

FormattedButtonSpec +

ButtonSpec  Object

+

Button spec used in Buttons constructor

Properties

-

server

+

sleep

-

string

+

(Array of number or number)

-

 

+

Yes

+

The number of milliseconds to wait before adding the next participant. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500]

+

Defaults to [250, 500].

-

user

+

autoSendInviteV4

-

string

+

boolean

-

 

+

Yes

+

If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default)

+

Defaults to true.

-

_serialized

+

comment

string

-

 

+

Yes

+

The comment to be added to an inviteV4 (empty string by default)

+

Defaults to ''.

@@ -1483,41 +1692,30 @@

Properties

- - - - - - @@ -1525,9 +1723,9 @@

Properties

-

GroupParticipant +

ContactId  Object

-

Group participant information

+

ID that represents a contact

Properties

-

buttonId

+

id

string

-

 

-
-
-

type

-
-

number

-
-

 

+

Yes

+

Custom ID to set on the button. A random one will be generated if one is not passed.

-

buttonText

+

body

-

Object

+

string

 

+

The text to show on the button.

@@ -1542,10 +1740,10 @@

Properties

-

id

+

server

-

ContactId

+

string

 

@@ -1555,10 +1753,10 @@

Properties

-

isAdmin

+

user

-

boolean

+

string

 

@@ -1568,10 +1766,10 @@

Properties

-

isSuperAdmin

+

_serialized

-

boolean

+

string

 

@@ -1584,9 +1782,9 @@

Properties

-

MessageInfo +

CreateGroupOptions  Object

-

Message Info

+

An object that handles options for group creation

Properties

@@ -1601,30 +1799,802 @@

Properties

+ + + + + + + + + + + + + + +
-

delivery

+

messageTimer

-

Array of {id: ContactId, t: number}

+

number

-

 

+

Yes

-

Contacts to which the message has been delivered to

+

The number of seconds for the messages to disappear in the group (0 by default, won't take an effect if the group is been creating with myself only)

+

Defaults to 0.

-

deliveryRemaining

+

parentGroupId

-

number

+

(string or undefined)

 

-

Amount of people to whom the message has not been delivered to

+

The ID of a parent community group to link the newly created group with (won't take an effect if the group is been creating with myself only)

+
+

autoSendInviteV4

+
+

boolean

+
+

Yes

+
+

If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default)

+

Defaults to true.

+
+

comment

+
+

string

+
+

Yes

+
+

The comment to be added to an inviteV4 (empty string by default)

+

Defaults to ''.

+
+
+
+
+

CreateGroupResult +  Object

+

An object that handles the result for createGroup method

+
+

Properties

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

title

+
+

string

+
+

 

+
+

A group title

+
+

gid

+
+

Object

+
+

 

+
+

An object that handles the newly created group ID

+

Values in gid have the following properties:

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

server

+
+

string

+
+

 

+
+
+

user

+
+

string

+
+

 

+
+
+

_serialized

+
+

string

+
+

 

+
+
+
+

participants

+
+

Object with ParticipantResult properties

+
+

 

+
+

An object that handles the result value for each added to the group participant

+
+
+
+
+

FormattedButtonSpec +  Object

+
+

Properties

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

buttonId

+
+

string

+
+

 

+
+
+

type

+
+

number

+
+

 

+
+
+

buttonText

+
+

Object

+
+

 

+
+
+
+
+
+

GroupMembershipRequest +  Object

+

An object that handles the information about the group membership request

+
+

Properties

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

id

+
+

Object

+
+

 

+
+

The wid of a user who requests to enter the group

+
+

addedBy

+
+

Object

+
+

 

+
+

The wid of a user who created that request

+
+

parentGroupId

+
+

(Object or null)

+
+

 

+
+

The wid of a community parent group to which the current group is linked

+
+

requestMethod

+
+

string

+
+

 

+
+

The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin

+
+

t

+
+

number

+
+

 

+
+

The timestamp the request was created at

+
+
+
+
+

GroupMembershipRequest +  Object

+

An object that handles the information about the group membership request

+
+

Properties

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

id

+
+

Object

+
+

 

+
+

The wid of a user who requests to enter the group

+
+

addedBy

+
+

Object

+
+

 

+
+

The wid of a user who created that request

+
+

parentGroupId

+
+

(Object or null)

+
+

 

+
+

The wid of a community parent group to which the current group is linked

+
+

requestMethod

+
+

string

+
+

 

+
+

The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin

+
+

t

+
+

number

+
+

 

+
+

The timestamp the request was created at

+
+
+
+
+

GroupParticipant +  Object

+

Group participant information

+
+

Properties

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

id

+
+

ContactId

+
+

 

+
+
+

isAdmin

+
+

boolean

+
+

 

+
+
+

isSuperAdmin

+
+

boolean

+
+

 

+
+
+
+
+
+

LocationSendOptions +  Object

+

Location send options

+
+

Properties

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

name

+
+

string

+
+

Yes

+
+

Location name

+
+

address

+
+

string

+
+

Yes

+
+

Location address

+
+

url

+
+

string

+
+

Yes

+
+

URL address to be shown within a location message

+
+
+
+
+

MembershipRequestActionOptions +  Object

+

An object that handles options for approveGroupMembershipRequests and rejectGroupMembershipRequests methods

+
+

Properties

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

requesterIds

+
+

(Array of string, string, or null)

+
+

 

+
+

User ID/s who requested to join the group, if no value is provided, the method will search for all membership requests for that group

+
+

sleep

+
+

(Array of number, number, or null)

+
+

 

+
+

The number of milliseconds to wait before performing an operation for the next requester. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500]

+
+
+
+
+

MembershipRequestActionOptions +  Object

+

An object that handles options for approveGroupMembershipRequests and rejectGroupMembershipRequests methods

+
+

Properties

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

requesterIds

+
+

(Array of string, string, or null)

+
+

 

+
+

User ID/s who requested to join the group, if no value is provided, the method will search for all membership requests for that group

+
+

sleep

+
+

(Array of number, number, or null)

+
+

 

+
+

The number of milliseconds to wait before performing an operation for the next requester. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500]

+
+
+
+
+

MembershipRequestActionResult +  Object

+

An object that handles the result for membership request action

+
+

Properties

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

requesterId

+
+

string

+
+

 

+
+

User ID whos membership request was approved/rejected

+
+

error

+
+

(number or undefined)

+
+

 

+
+

An error code that occurred during the operation for the participant

+
+

message

+
+

string

+
+

 

+
+

A message with a result of membership request action

+
+
+
+
+

MembershipRequestActionResult +  Object

+

An object that handles the result for membership request action

+
+

Properties

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

requesterId

+
+

string

+
+

 

+
+

User ID whos membership request was approved/rejected

+
+

error

+
+

number

+
+

 

+
+

An error code that occurred during the operation for the participant

+
+

message

+
+

string

+
+

 

+
+

A message with a result of membership request action

+
+
+
+
+

MessageInfo +  Object

+

Message Info

+
+

Properties

+ + + + + + + + + + + + + + + + + + + + + @@ -1729,7 +2699,7 @@

Properties

Yes

@@ -1778,6 +2748,21 @@

Properties

Defaults to false.

+ + + + + +
NameTypeOptionalDescription
+

delivery

+
+

Array of {id: ContactId, t: number}

+
+

 

+
+

Contacts to which the message has been delivered to

+
+

deliveryRemaining

+
+

number

+
+

 

+
+

Amount of people to whom the message has not been delivered to

-

Send audio as voice message

+

Send audio as voice message with a generated waveform

Defaults to false.

+

isViewOnce

+
+

boolean

+
+

Yes

+
+

Send photo/video as a view once message

+

Defaults to false.

+

parseVCards

@@ -1911,6 +2896,208 @@

Properties

+

ParticipantResult +  Object

+

An object that represents the result for a participant added to a group

+
+

Properties

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

statusCode

+
+

number

+
+

 

+
+

The status code of the result

+
+

message

+
+

string

+
+

 

+
+

The result message

+
+

isGroupCreator

+
+

boolean

+
+

 

+
+

Indicates if the participant is a group creator

+
+

isInviteV4Sent

+
+

boolean

+
+

 

+
+

Indicates if the inviteV4 was sent to the participant

+
+
+
+
+

PollSendOptions +  Object

+

Poll send options

+
+

Properties

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

allowMultipleAnswers

+
+

boolean

+
+

Yes

+
+

If false it is a single choice poll, otherwise it is a multiple choice poll (false by default)

+

Defaults to false.

+
+

messageSecret

+
+

Array of number

+
+

 

+
+

The custom message secret, can be used as a poll ID. NOTE: it has to be a unique vector with a length of 32

+

Value can be null.

+
+
+
+
+

ReactionList +  Object

+

Reaction List

+
+

Properties

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

id

+
+

string

+
+

 

+
+

Original emoji

+
+

aggregateEmoji

+
+

string

+
+

 

+
+

aggregate emoji

+
+

hasReactionByMe

+
+

boolean

+
+

 

+
+

Flag who sent the reaction

+
+

senders

+
+

Array of Reaction

+
+

 

+
+

Reaction senders, to this message

+
+
+
+

StickerMetadata  Object

Sticker metadata.

@@ -1970,6 +3157,68 @@

Properties

+

TargetOptions +  Object

+

Target options object description

+
+

Properties

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

moduleId

+
+

(string or number)

+
+

 

+
+

The name or a key of the target module to search

+
+

index

+
+

number

+
+

 

+
+

The index value of the target module

+
+

property

+
+

string

+
+

 

+
+

The function name to get from a module

+
+
+
+
@@ -1980,7 +3229,7 @@

Properties

diff --git a/docs/index.html b/docs/index.html index caff23885a..e29c2b6a8c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,53 +1,52 @@ - - - - - whatsapp-web.js 1.17.1 » Home - - - - - - - - + +
+
+
+ +
+ + +

npm Depfu WhatsApp_Web 2.2346.52 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.

+

NOTE: I can't guarantee you will not be blocked by using this method, although it has worked for me. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe.

+

Quick Links

+ +

Installation

+

The module is now available on npm! npm i whatsapp-web.js

+

Please note that Node v12+ is required.

+

Example usage

+
const { Client } = require('whatsapp-web.js');
 
 const client = new Client();
 
@@ -68,3106 +67,3682 @@ 

Example usage

client.initialize();
-

Take a look at example.js for another example with more use cases.

-

For more information on saving and restoring sessions, check out the available Authentication Strategies.

-

Supported features

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeatureStatus
Multi Device
Send messages
Receive messages
Send media (images/audio/documents)
Send media (video)(requires google chrome)
Send stickers
Receive media (images/audio/video/documents)
Send contact cards
Send location
Send buttons
Send lists✅ (business accounts not supported)
Receive location
Message replies
Join groups by invite
Get invite for group
Modify group info (subject, description)
Modify group settings (send messages, edit info)
Add group participants
Kick group participants
Promote/demote group participants
Mention users
Mute/unmute chats
Block/unblock contacts
Get contact info
Get profile pictures
Set user status message
React to messages
-

Something missing? Make an issue and let us know!

-

Contributing

-

Pull requests are welcome! If you see something you'd like to add, please do. For drastic changes, please open an issue first.

-

Supporting the project

-

You can support the maintainer of this project through the links below

- -

Disclaimer

-

This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or its affiliates. The official WhatsApp website can be found at https://whatsapp.com. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners.

-

License

-

Copyright 2019 Pedro S Lopez

-

Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this project except in compliance with the License. - You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

-

Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License.

-
-
-
-
+

Take a look at example.js for another example with more use cases.

+

For more information on saving and restoring sessions, check out the available Authentication Strategies.

+

Supported features

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureStatus
Multi Device
Send messages
Receive messages
Send media (images/audio/documents)
Send media (video)(requires google chrome)
Send stickers
Receive media (images/audio/video/documents)
Send contact cards
Send location
Send buttons
Send lists✅ (business accounts not supported)
Receive location
Message replies
Join groups by invite
Get invite for group
Modify group info (subject, description)
Modify group settings (send messages, edit info)
Add group participants
Kick group participants
Promote/demote group participants
Mention users
Mute/unmute chats
Block/unblock contacts
Get contact info
Get profile pictures
Set user status message
React to messages
+

Something missing? Make an issue and let us know!

+

Contributing

+

Pull requests are welcome! If you see something you'd like to add, please do. For drastic changes, please open an issue first.

+

Supporting the project

+

You can support the maintainer of this project through the links below

+ +

Disclaimer

+

This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or its affiliates. The official WhatsApp website can be found at https://whatsapp.com. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners.

+

License

+

Copyright 2019 Pedro S Lopez

+

Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this project except in compliance with the License. +You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

+

Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.

+ +
+
+

Base

-
-
-
- Base() -
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Base() +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
-
-
-
-
+
+
+
+

BaseAuthStrategy

-
-
-
- BaseAuthStrategy() -
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ BaseAuthStrategy() +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
-
-
-
-
+
+
+
+

BusinessContact

- - - + + + + + +
-
-
-
-
+
+
+
+

Buttons

- -
-
-
- Buttons#body -
-
-
-
- Buttons#buttons -
-
-
-
-
-
-
-
- Buttons#footer -
-
-
-
- Buttons#title -
-
-
-
-
+ + +
+
+
+ Buttons#body +
+
+
+
+ Buttons#buttons +
+
+
+
+
+ +
+
+
+ Buttons#footer +
+
+
+
+ Buttons#title +
+
+
+
+
+
-
-
-
-
+
+
+
+

Call

-
-
-
- Call() -
-
-
-
- Call#canHandleLocally -
-
-
-
- Call#from -
-
-
-
- Call#fromMe -
-
-
-
-
-
-
-
- Call#id -
-
-
-
- Call#isGroup -
-
-
-
- Call#isVideo -
-
-
-
- Call#participants -
-
-
-
-
-
-
-
- Call#timestamp -
-
-
-
- Call#webClientShouldHandle -
-
-
-
-
+
+
+
+ Call() +
+
+
+
+ Call#canHandleLocally +
+
+
+
+ Call#from +
+
+
+
+ Call#fromMe +
+
+
+
+
+ +
+
+
+ Call#id +
+
+
+
+ Call#isGroup +
+
+
+
+ Call#isVideo +
+
+
+
+ Call#participants +
+
+
+
+
+ +
+
+
+ Call#reject() +
+
+
+
+ Call#timestamp +
+
+
+
+ Call#webClientShouldHandle +
+
+
+
+
+
-
-
-
-
+
+
+
+

Chat

-
-
-
- Chat() -
-
-
-
- Chat#archive() -
-
-
-
- Chat#archived -
-
-
-
- Chat#clearMessages() -
-
-
-
- Chat#clearState() -
-
-
-
- Chat#delete() -
-
-
-
- Chat#fetchMessages(searchOptions) -
-
-
-
- Chat#getContact() -
-
-
-
- Chat#getLabels() -
-
-
-
- Chat#id -
-
-
-
-
-
-
-
- Chat#isGroup -
-
-
-
- Chat#isMuted -
-
-
-
- Chat#isReadOnly -
-
-
-
- Chat#markUnread() -
-
-
-
- Chat#mute(unmuteDate) -
-
-
-
- Chat#muteExpiration -
-
-
-
- Chat#name -
-
-
-
- Chat#pin() -
-
-
-
- Chat#pinned -
-
-
-
- Chat#sendMessage(content[, options]) -
-
-
-
-
-
-
-
- Chat#sendSeen() -
-
-
-
- Chat#sendStateRecording() -
-
-
-
- Chat#sendStateTyping() -
-
-
-
- Chat#timestamp -
-
-
-
- Chat#unarchive() -
-
-
-
- Chat#unmute() -
-
-
-
- Chat#unpin() -
-
-
-
- Chat#unreadCount -
-
-
-
-
+ + +
+
+
+ Chat#id +
+
+
+
+ Chat#isGroup +
+
+
+
+ Chat#isMuted +
+
+
+
+ Chat#isReadOnly +
+
+
+
+ Chat#lastMessage +
+
+
+
+ Chat#markUnread() +
+
+
+
+ Chat#mute(unmuteDate) +
+
+
+
+ Chat#muteExpiration +
+
+
+
+ Chat#name +
+
+
+
+ Chat#pin() +
+
+
+
+
+ + +
-
-
-
-
+
+
+
+

ChatTypes

-
-
-
- ChatTypes.GROUP -
-
-
-
-
-
-
-
- ChatTypes.SOLO -
-
-
-
-
-
-
-
- ChatTypes.UNKNOWN -
-
-
-
-
+
+
+
+ ChatTypes.GROUP +
+
+
+
+
+ +
+
+
+ ChatTypes.SOLO +
+
+
+
+
+ +
+
+
+ ChatTypes.UNKNOWN +
+
+
+
+
+
-
-
-
-
+
+
+
+

Client

- - - + + + + + +
-
-
-
-
+
+
+
+

ClientInfo

-
-
-
- ClientInfo() -
-
-
-
- ClientInfo#getBatteryStatus() -
-
-
-
- ClientInfo#me -
-
-
-
-
-
-
-
- ClientInfo#phone -
-
-
-
- ClientInfo#platform -
-
-
-
- ClientInfo#pushname -
-
-
-
-
-
-
-
- ClientInfo#wid -
-
-
-
-
+
+
+
+ ClientInfo() +
+
+
+
+ ClientInfo#getBatteryStatus() +
+
+
+
+ ClientInfo#me +
+
+
+
+
+ +
+
+
+ ClientInfo#phone +
+
+
+
+ ClientInfo#platform +
+
+
+
+ ClientInfo#pushname +
+
+
+
+
+ +
+
+
+ ClientInfo#wid +
+
+
+
+
+
-
-
-
-
+
+
+
+

Contact

- -
-
-
- Contact#id -
-
-
-
- Contact#isBlocked -
-
-
-
- Contact#isBusiness -
-
-
-
- Contact#isEnterprise -
-
-
-
- Contact#isGroup -
-
-
-
- Contact#isMe -
-
-
-
- Contact#isMyContact -
-
-
-
- Contact#isUser -
-
-
-
-
-
-
-
- Contact#isWAContact -
-
-
-
- Contact#name -
-
-
-
- Contact#number -
-
-
-
- Contact#pushname -
-
-
-
- Contact#shortName -
-
-
-
- Contact#unblock() -
-
-
-
-
+ + +
+
+
+ Contact#id +
+
+
+
+ Contact#isBlocked +
+
+
+
+ Contact#isBusiness +
+
+
+
+ Contact#isEnterprise +
+
+
+
+ Contact#isGroup +
+
+
+
+ Contact#isMe +
+
+
+
+ Contact#isMyContact +
+
+
+
+ Contact#isUser +
+
+
+
+
+ +
+
+
+ Contact#isWAContact +
+
+
+
+ Contact#name +
+
+
+
+ Contact#number +
+
+
+
+ Contact#pushname +
+
+
+
+ Contact#shortName +
+
+
+
+ Contact#unblock() +
+
+
+
+
+
-
-
-
-
+
+
+
+ -
-
-
+
+
+
+

GroupChat

- - - + + + + + +
-
-
-
-
+
+
+
+ -
-
-
+
+
+
+ -
-
-
+
+
+
+ -
-
-
+
+
+
+

Label

-
-
-
- Label(client, labelData) -
-
-
-
- Label#getChats() -
-
-
-
-
-
-
-
- Label#hexColor -
-
-
-
- Label#id -
-
-
-
-
-
-
-
- Label#name -
-
-
-
-
+
+
+
+ Label(client, labelData) +
+
+
+
+ Label#getChats() +
+
+
+
+
+ +
+
+
+ Label#hexColor +
+
+
+
+ Label#id +
+
+
+
+
+ +
+
+
+ Label#name +
+
+
+
+
+
-
-
-
-
+
+
+
+

LegacySessionAuth

-
-
-
- LegacySessionAuth(options) -
-
-
-
-
-
-
-
-
-
-
-
-
+ + +
+
+
+
+ +
+
+
+
+
-
-
-
-
+
+
+
+

List

- -
-
-
- List#description -
-
-
-
- List#footer -
-
-
-
- List#sections -
-
-
-
-
-
-
-
- List#title -
-
-
-
-
+ + +
+
+
+ List#description +
+
+
+
+ List#footer +
+
+
+
+ List#sections +
+
+
+
+
+ +
+
+
+ List#title +
+
+
+
+
+
-
-
-
-
+
+
+
+

LocalAuth

-
-
-
- LocalAuth(options) -
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ LocalAuth(options) +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+

LocalWebCache

+
+
+
+
+ LocalWebCache(options) +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
-
-
-
-
+
+
+
+

Location

- -
-
-
- Location#latitude -
-
-
-
- Location#longitude -
-
-
-
-
-
-
-
-
+ + +
+
+
+ Location#latitude +
+
+
+
+ Location#longitude +
+
+
+
+ Location#name +
+
+
+
+
+ +
+
+
+ Location#url +
+
+
+
+
+
-
-
-
-
+
+
+
+

Message

- - - + + + + + +
-
-
-
-
+
+
+
+

MessageAck

-
-
-
- MessageAck.ACK_DEVICE -
-
-
-
- MessageAck.ACK_ERROR -
-
-
-
-
-
-
-
- MessageAck.ACK_PENDING -
-
-
-
- MessageAck.ACK_PLAYED -
-
-
-
-
-
-
-
- MessageAck.ACK_READ -
-
-
-
- MessageAck.ACK_SERVER -
-
-
-
-
+
+
+
+ MessageAck.ACK_DEVICE +
+
+
+
+ MessageAck.ACK_ERROR +
+
+
+
+
+ + + +
+
+
+ MessageAck.ACK_READ +
+
+
+
+ MessageAck.ACK_SERVER +
+
+
+
+
+
-
-
-
-
+
+
+
+ -
-
-
+
+
+
+

MessageTypes

- - - + + + + + +
-
-
-
-
+
+
+
+

NoAuth

-
-
-
- NoAuth() -
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ NoAuth() +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
-
-
-
-
+
+
+
+

Order

-
-
-
- Order() -
-
-
-
- Order#createdAt -
-
-
-
-
-
-
-
- Order#currency -
-
-
-
- Order#subtotal -
-
-
-
-
-
-
-
- Order#total -
-
-
-
-
+
+
+
+ Order() +
+
+
+
+ Order#createdAt +
+
+
+
+
+ +
+
+
+ Order#currency +
+
+
+
+ Order#subtotal +
+
+
+
+
+ +
+
+
+ Order#total +
+
+
+
+
+
-
-
-
-
+
+
+
+ -
-
-
+
+
+
+
+

Poll

+
+ + +
+
+
+ Poll#pollName +
+
+
+
+ Poll#pollOptions +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+

PrivateChat

- - - + + + + + +
-
-
-
-
+
+
+
+

PrivateContact

- - - + + + + + +
-
-
-
-
+
+
+
+

Product

-
-
-
- Product() -
-
-
-
- Product#currency -
-
-
-
- Product#data -
-
-
-
-
-
-
-
- Product#id -
-
-
-
- Product#name -
-
-
-
- Product#price -
-
-
-
-
-
-
-
- Product#quantity -
-
-
-
- Product#thumbnailUrl -
-
-
-
-
+
+
+
+ Product() +
+
+
+
+ Product#currency +
+
+
+
+ Product#data +
+
+
+
+
+ +
+
+
+ Product#id +
+
+
+
+ Product#name +
+
+
+
+ Product#price +
+
+
+
+
+ +
+
+
+ Product#quantity +
+
+
+
+ Product#thumbnailUrl +
+
+
+
+
+
-
-
-
-
+
+
+
+

ProductMetadata

- - -
-
-
-
+ + + + +
+
+
+
+ +
+
+
+
+
+

Reaction

+
+
+
+
+ Reaction() +
+
+
+
+ Reaction#ack +
+
+
+
+ Reaction#id +
+
+
+
+ Reaction#msgId +
+
+
+
+
+ +
+
+
+ Reaction#orphan +
+
+
+
+ Reaction#orphanReason +
+
+
+
+ Reaction#reaction +
+
+
+
+ Reaction#read +
+
+
+
+
+ +
+
+
+ Reaction#senderId +
+
+
+
+ Reaction#timestamp +
+
+
+
+
+ +
+
+
+
+
+

RemoteAuth

+
+
+
+
+ RemoteAuth(options) +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
-
-
-
-
+
+
+
+
+

RemoteWebCache

+
+
+
+
+ RemoteWebCache(options) +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+

Status

-
-
-
- Status.AUTHENTICATING -
-
-
-
-
-
-
-
- Status.INITIALIZING -
-
-
-
-
-
-
-
- Status.READY -
-
-
-
-
+
+
+
+ Status.AUTHENTICATING +
+
+
+
+
+ +
+
+
+ Status.INITIALIZING +
+
+
+
+
+ +
+
+
+ Status.READY +
+
+
+
+
+
-
-
-
-
+
+
+
+ -
-
-
+
+
+
+

WAState

-
-
-
- WAState.CONFLICT -
-
-
-
- WAState.CONNECTED -
-
-
-
- WAState.DEPRECATED_VERSION -
-
-
-
- WAState.OPENING -
-
-
-
-
-
-
-
- WAState.PAIRING -
-
-
-
- WAState.PROXYBLOCK -
-
-
-
- WAState.SMB_TOS_BLOCK -
-
-
-
- WAState.TIMEOUT -
-
-
-
-
-
-
-
- WAState.TOS_BLOCK -
-
-
-
- WAState.UNLAUNCHED -
-
-
-
- WAState.UNPAIRED -
-
-
-
- WAState.UNPAIRED_IDLE -
-
-
-
-
+
+
+
+ WAState.CONFLICT +
+
+
+
+ WAState.CONNECTED +
+
+
+
+ WAState.DEPRECATED_VERSION +
+
+
+
+ WAState.OPENING +
+
+
+
+
+ +
+
+
+ WAState.PAIRING +
+
+
+
+ WAState.PROXYBLOCK +
+
+
+
+ WAState.SMB_TOS_BLOCK +
+
+
+
+ WAState.TIMEOUT +
+
+
+
+
+ +
+
+
+ WAState.TOS_BLOCK +
+
+
+
+ WAState.UNLAUNCHED +
+
+
+
+ WAState.UNPAIRED +
+
+
+
+ WAState.UNPAIRED_IDLE +
+
+
+
+
+
-
-
-
-
-
- -
-
-
- -
- - - - - - - - - \ No newline at end of file +
+ +
+
+

WebCache

+
+
+
+
+ WebCache() +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+ + + + + + +
+ +
+ + + + + + + + diff --git a/docs/scripts/jsdoc-toc.js b/docs/scripts/jsdoc-toc.js index 62e7e8f478..80c357faee 100644 --- a/docs/scripts/jsdoc-toc.js +++ b/docs/scripts/jsdoc-toc.js @@ -6,7 +6,7 @@ treeNode.tree({ autoEscape: false, closedIcon: '⇢', - data: [{"label":"Globals","id":"global","children":[]},{"label":"Base","id":"Base","children":[]},{"label":"BaseAuthStrategy","id":"BaseAuthStrategy","children":[]},{"label":"BusinessContact","id":"BusinessContact","children":[]},{"label":"Buttons","id":"Buttons","children":[]},{"label":"Call","id":"Call","children":[]},{"label":"Chat","id":"Chat","children":[]},{"label":"Client","id":"Client","children":[]},{"label":"ClientInfo","id":"ClientInfo","children":[]},{"label":"Contact","id":"Contact","children":[]},{"label":"GroupChat","id":"GroupChat","children":[]},{"label":"GroupNotification","id":"GroupNotification","children":[]},{"label":"InterfaceController","id":"InterfaceController","children":[]},{"label":"Label","id":"Label","children":[]},{"label":"LegacySessionAuth","id":"LegacySessionAuth","children":[]},{"label":"List","id":"List","children":[]},{"label":"LocalAuth","id":"LocalAuth","children":[]},{"label":"Location","id":"Location","children":[]},{"label":"Message","id":"Message","children":[]},{"label":"MessageMedia","id":"MessageMedia","children":[]},{"label":"NoAuth","id":"NoAuth","children":[]},{"label":"Order","id":"Order","children":[]},{"label":"PrivateChat","id":"PrivateChat","children":[]},{"label":"PrivateContact","id":"PrivateContact","children":[]},{"label":"Product","id":"Product","children":[]},{"label":"Util","id":"Util","children":[]}], + data: [{"label":"Globals","id":"global","children":[]},{"label":"Base","id":"Base","children":[]},{"label":"BaseAuthStrategy","id":"BaseAuthStrategy","children":[]},{"label":"BusinessContact","id":"BusinessContact","children":[]},{"label":"Buttons","id":"Buttons","children":[]},{"label":"Call","id":"Call","children":[]},{"label":"Chat","id":"Chat","children":[]},{"label":"Client","id":"Client","children":[]},{"label":"ClientInfo","id":"ClientInfo","children":[]},{"label":"Contact","id":"Contact","children":[]},{"label":"GroupChat","id":"GroupChat","children":[]},{"label":"GroupNotification","id":"GroupNotification","children":[]},{"label":"InterfaceController","id":"InterfaceController","children":[]},{"label":"Label","id":"Label","children":[]},{"label":"LegacySessionAuth","id":"LegacySessionAuth","children":[]},{"label":"List","id":"List","children":[]},{"label":"LocalAuth","id":"LocalAuth","children":[]},{"label":"LocalWebCache","id":"LocalWebCache","children":[]},{"label":"Location","id":"Location","children":[]},{"label":"Message","id":"Message","children":[]},{"label":"MessageMedia","id":"MessageMedia","children":[]},{"label":"NoAuth","id":"NoAuth","children":[]},{"label":"Order","id":"Order","children":[]},{"label":"Poll","id":"Poll","children":[]},{"label":"PrivateChat","id":"PrivateChat","children":[]},{"label":"PrivateContact","id":"PrivateContact","children":[]},{"label":"Product","id":"Product","children":[]},{"label":"Reaction","id":"Reaction","children":[]},{"label":"RemoteAuth","id":"RemoteAuth","children":[]},{"label":"RemoteWebCache","id":"RemoteWebCache","children":[]},{"label":"Util","id":"Util","children":[]},{"label":"WebCache","id":"WebCache","children":[]}], openedIcon: ' ⇣', saveState: false, useContextMenu: false diff --git a/docs/structures_Base.js.html b/docs/structures_Base.js.html index b62302b36f..dc4ee1f491 100644 --- a/docs/structures_Base.js.html +++ b/docs/structures_Base.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Base.js + whatsapp-web.js 1.23.0 » Source: structures/Base.js @@ -15,7 +15,7 @@ @@ -60,7 +60,7 @@

Source: structures/Base.js

diff --git a/docs/structures_BusinessContact.js.html b/docs/structures_BusinessContact.js.html index 1569c3322b..54b5345264 100644 --- a/docs/structures_BusinessContact.js.html +++ b/docs/structures_BusinessContact.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/BusinessContact.js + whatsapp-web.js 1.23.0 » Source: structures/BusinessContact.js @@ -15,7 +15,7 @@ @@ -59,7 +59,7 @@

Source: structures/BusinessContact.js

diff --git a/docs/structures_Buttons.js.html b/docs/structures_Buttons.js.html index f11871752d..0ccc18407a 100644 --- a/docs/structures_Buttons.js.html +++ b/docs/structures_Buttons.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Buttons.js + whatsapp-web.js 1.23.0 » Source: structures/Buttons.js @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@

Source: structures/Buttons.js

diff --git a/docs/structures_Call.js.html b/docs/structures_Call.js.html index cfe7007a70..089363cc81 100644 --- a/docs/structures_Call.js.html +++ b/docs/structures_Call.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Call.js + whatsapp-web.js 1.23.0 » Source: structures/Call.js @@ -15,7 +15,7 @@ @@ -93,7 +93,15 @@

Source: structures/Call.js

return super._patch(data); } - + + /** + * Reject the call + */ + async reject() { + return this.client.pupPage.evaluate((peerJid, id) => { + return window.WWebJS.rejectCall(peerJid, id); + }, this.from, this.id); + } } module.exports = Call;
@@ -106,7 +114,7 @@

Source: structures/Call.js

diff --git a/docs/structures_Chat.js.html b/docs/structures_Chat.js.html index 09d1986a1c..c9dec6e2ed 100644 --- a/docs/structures_Chat.js.html +++ b/docs/structures_Chat.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Chat.js + whatsapp-web.js 1.23.0 » Source: structures/Chat.js @@ -15,7 +15,7 @@ @@ -106,6 +106,12 @@

Source: structures/Chat.js

*/ this.muteExpiration = data.muteExpiration; + /** + * Last message fo chat + * @type {Message} + */ + this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined; + return super._patch(data); } @@ -201,13 +207,22 @@

Source: structures/Chat.js

/** * Loads chat messages, sorted from earliest to latest. - * @param {Object} searchOptions Options for searching messages. Right now only limit is supported. + * @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported. * @param {Number} [searchOptions.limit] The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages. + * @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined. * @returns {Promise&lt;Array&lt;Message>>} */ async fetchMessages(searchOptions) { let messages = await this.client.pupPage.evaluate(async (chatId, searchOptions) => { - const msgFilter = m => !m.isNotification; // dont include notification messages + const msgFilter = (m) => { + if (m.isNotification) { + return false; // dont include notification messages + } + if (searchOptions &amp;&amp; searchOptions.fromMe !== undefined &amp;&amp; m.id.fromMe !== searchOptions.fromMe) { + return false; + } + return true; + }; const chat = window.Store.Chat.get(chatId); let msgs = chat.msgs.getModelsArray().filter(msgFilter); @@ -277,6 +292,15 @@

Source: structures/Chat.js

async getLabels() { return this.client.getChatLabels(this.id._serialized); } + + /** + * Add or remove labels to this Chat + * @param {Array&lt;number|string>} labelIds + * @returns {Promise&lt;void>} + */ + async changeLabels(labelIds) { + return this.client.addOrRemoveLabels(labelIds, [this.id._serialized]); + } } module.exports = Chat; @@ -290,7 +314,7 @@

Source: structures/Chat.js

diff --git a/docs/structures_ClientInfo.js.html b/docs/structures_ClientInfo.js.html index fe6a725325..4c19f3c7d9 100644 --- a/docs/structures_ClientInfo.js.html +++ b/docs/structures_ClientInfo.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/ClientInfo.js + whatsapp-web.js 1.23.0 » Source: structures/ClientInfo.js @@ -15,7 +15,7 @@ @@ -109,7 +109,7 @@

Source: structures/ClientInfo.js

diff --git a/docs/structures_Contact.js.html b/docs/structures_Contact.js.html index 1344f74dcf..06a1e0aba9 100644 --- a/docs/structures_Contact.js.html +++ b/docs/structures_Contact.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Contact.js + whatsapp-web.js 1.23.0 » Source: structures/Contact.js @@ -15,7 +15,7 @@ @@ -187,9 +187,10 @@

Source: structures/Contact.js

await this.client.pupPage.evaluate(async (contactId) => { const contact = window.Store.Contact.get(contactId); - await window.Store.BlockContact.blockContact(contact); + await window.Store.BlockContact.blockContact({contact}); }, this.id._serialized); + this.isBlocked = true; return true; } @@ -205,6 +206,7 @@

Source: structures/Contact.js

await window.Store.BlockContact.unblockContact(contact); }, this.id._serialized); + this.isBlocked = false; return true; } @@ -245,7 +247,7 @@

Source: structures/Contact.js

diff --git a/docs/structures_GroupChat.js.html b/docs/structures_GroupChat.js.html index c0d97defc7..319c776520 100644 --- a/docs/structures_GroupChat.js.html +++ b/docs/structures_GroupChat.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/GroupChat.js + whatsapp-web.js 1.23.0 » Source: structures/GroupChat.js @@ -15,7 +15,7 @@ @@ -84,29 +84,151 @@

Source: structures/GroupChat.js

return this.groupMetadata.participants; } + /** + * An object that handles the result for {@link addParticipants} method + * @typedef {Object} AddParticipantsResult + * @property {number} code The code of the result + * @property {string} message The result message + * @property {boolean} isInviteV4Sent Indicates if the inviteV4 was sent to the partitipant + */ + + /** + * An object that handles options for adding participants + * @typedef {Object} AddParticipnatsOptions + * @property {Array&lt;number>|number} [sleep = [250, 500]] The number of milliseconds to wait before adding the next participant. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500] + * @property {boolean} [autoSendInviteV4 = true] If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default) + * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default) + */ + /** * Adds a list of participants by ID to the group - * @param {Array&lt;string>} participantIds - * @returns {Promise&lt;Object>} + * @param {string|Array&lt;string>} participantIds + * @param {AddParticipnatsOptions} options An object thay handles options for adding participants + * @returns {Promise&lt;Object.&lt;string, AddParticipantsResult>|string>} Returns an object with the resulting data or an error message as a string */ - async addParticipants(participantIds) { - return await this.client.pupPage.evaluate((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); + async addParticipants(participantIds, options = {}) { + return await this.client.pupPage.evaluate(async (groupId, participantIds, options) => { + const { sleep = [250, 500], autoSendInviteV4 = true, comment = '' } = options; + const participantData = {}; + + !Array.isArray(participantIds) &amp;&amp; (participantIds = [participantIds]); + const groupWid = window.Store.WidFactory.createWid(groupId); + const group = await window.Store.Chat.find(groupWid); + const participantWids = participantIds.map((p) => window.Store.WidFactory.createWid(p)); + + const errorCodes = { + default: 'An unknown error occupied while adding a participant', + isGroupEmpty: 'AddParticipantsError: The participant can\'t be added to an empty group', + iAmNotAdmin: 'AddParticipantsError: You have no admin rights to add a participant to a group', + 200: 'The participant was added successfully', + 403: 'The participant can be added by sending private invitation only', + 404: 'The phone number is not registered on WhatsApp', + 408: 'You cannot add this participant because they recently left the group', + 409: 'The participant is already a group member', + 417: 'The participant can\'t be added to the community. You can invite them privately to join this group through its invite link', + 419: 'The participant can\'t be added because the group is full' + }; + + await window.Store.GroupMetadata.queryAndUpdate(groupWid); + const groupMetadata = group.groupMetadata; + const groupParticipants = groupMetadata?.participants; + + if (!groupParticipants) { + return errorCodes.isGroupEmpty; + } + + if (!group.iAmAdmin()) { + return errorCodes.iAmNotAdmin; + } + + const _getSleepTime = (sleep) => { + if (!Array.isArray(sleep) || sleep.length === 2 &amp;&amp; sleep[0] === sleep[1]) { + return sleep; + } + if (sleep.length === 1) { + return sleep[0]; + } + (sleep[1] - sleep[0]) &lt; 100 &amp;&amp; (sleep[0] = sleep[1]) &amp;&amp; (sleep[1] += 100); + return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0]; + }; + + for (const pWid of participantWids) { + const pId = pWid._serialized; + + participantData[pId] = { + code: undefined, + message: undefined, + isInviteV4Sent: false + }; + + if (groupParticipants.some(p => p.id._serialized === pId)) { + participantData[pId].code = 409; + participantData[pId].message = errorCodes[409]; + continue; + } + + if (!(await window.Store.QueryExist(pWid))?.wid) { + participantData[pId].code = 404; + participantData[pId].message = errorCodes[404]; + continue; + } + + const rpcResult = + await window.WWebJS.getAddParticipantsRpcResult(groupMetadata, groupWid, pWid); + const { code: rpcResultCode } = rpcResult; + + participantData[pId].code = rpcResultCode; + participantData[pId].message = + errorCodes[rpcResultCode] || errorCodes.default; + + if (autoSendInviteV4 &amp;&amp; rpcResultCode === 403) { + let userChat, isInviteV4Sent = false; + window.Store.ContactCollection.gadd(pWid, { silent: true }); + + if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' &amp;&amp; + (userChat = await window.Store.Chat.find(pWid))) { + const groupName = group.formattedTitle || group.name; + const res = await window.Store.GroupInviteV4.sendGroupInviteMessage( + userChat, + group.id._serialized, + groupName, + rpcResult.inviteV4Code, + rpcResult.inviteV4CodeExp, + comment, + await window.WWebJS.getProfilePicThumbToBase64(groupWid) + ); + isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '&lt;', '2.2335.6') + ? res === 'OK' + : res.messageSendResult === 'OK'; + } + + participantData[pId].isInviteV4Sent = isInviteV4Sent; + } + + sleep &amp;&amp; + participantWids.length > 1 &amp;&amp; + participantWids.indexOf(pWid) !== participantWids.length - 1 &amp;&amp; + (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep)))); + } + + return participantData; + }, this.id._serialized, participantIds, options); } /** * Removes a list of participants by ID to the group * @param {Array&lt;string>} participantIds - * @returns {Promise&lt;Object>} + * @returns {Promise&lt;{ status: number }>} */ async removeParticipants(participantIds) { - return await this.client.pupPage.evaluate((chatId, participantIds) => { + return await this.client.pupPage.evaluate(async (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); + const chat = await window.Store.Chat.find(chatWid); + const participants = participantIds.map(p => { + return chat.groupMetadata.participants.get(p); + }).filter(p => Boolean(p)); + await window.Store.GroupParticipants.removeParticipants(chat, participants); + return { status: 200 }; }, this.id._serialized, participantIds); } @@ -116,10 +238,14 @@

Source: structures/GroupChat.js

* @returns {Promise&lt;{ status: number }>} Object with status code indicating if the operation was successful */ async promoteParticipants(participantIds) { - return await this.client.pupPage.evaluate((chatId, participantIds) => { + return await this.client.pupPage.evaluate(async (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); + const chat = await window.Store.Chat.find(chatWid); + const participants = participantIds.map(p => { + return chat.groupMetadata.participants.get(p); + }).filter(p => Boolean(p)); + await window.Store.GroupParticipants.promoteParticipants(chat, participants); + return { status: 200 }; }, this.id._serialized, participantIds); } @@ -129,10 +255,14 @@

Source: structures/GroupChat.js

* @returns {Promise&lt;{ status: number }>} Object with status code indicating if the operation was successful */ async demoteParticipants(participantIds) { - return await this.client.pupPage.evaluate((chatId, participantIds) => { + return await this.client.pupPage.evaluate(async (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); + const chat = await window.Store.Chat.find(chatWid); + const participants = participantIds.map(p => { + return chat.groupMetadata.participants.get(p); + }).filter(p => Boolean(p)); + await window.Store.GroupParticipants.demoteParticipants(chat, participants); + return { status: 200 }; }, this.id._serialized, participantIds); } @@ -145,7 +275,8 @@

Source: structures/GroupChat.js

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); + await window.Store.GroupUtils.setGroupSubject(chatWid, subject); + return true; } catch (err) { if(err.name === 'ServerStatusCodeError') return false; throw err; @@ -166,8 +297,10 @@

Source: structures/GroupChat.js

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; + let newId = await window.Store.MsgKey.newId(); try { - return await window.Store.GroupUtils.sendSetGroupDescription(chatWid, description, window.Store.MsgKey.newId(), descId); + await window.Store.GroupUtils.setGroupDescription(chatWid, description, newId, descId); + return true; } catch (err) { if(err.name === 'ServerStatusCodeError') return false; throw err; @@ -188,7 +321,8 @@

Source: structures/GroupChat.js

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); + await window.Store.GroupUtils.setGroupProperty(chatWid, 'announcement', adminsOnly ? 1 : 0); + return true; } catch (err) { if(err.name === 'ServerStatusCodeError') return false; throw err; @@ -210,7 +344,8 @@

Source: structures/GroupChat.js

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); + await window.Store.GroupUtils.setGroupProperty(chatWid, 'restrict', adminsOnly ? 1 : 0); + return true; } catch (err) { if(err.name === 'ServerStatusCodeError') return false; throw err; @@ -223,17 +358,42 @@

Source: structures/GroupChat.js

return true; } + /** + * Deletes the group's picture. + * @returns {Promise&lt;boolean>} Returns true if the picture was properly deleted. This can return false if the user does not have the necessary permissions. + */ + async deletePicture() { + const success = await this.client.pupPage.evaluate((chatid) => { + return window.WWebJS.deletePicture(chatid); + }, this.id._serialized); + + return success; + } + + /** + * Sets the group's picture. + * @param {MessageMedia} media + * @returns {Promise&lt;boolean>} Returns true if the picture was properly updated. This can return false if the user does not have the necessary permissions. + */ + async setPicture(media) { + const success = await this.client.pupPage.evaluate((chatid, media) => { + return window.WWebJS.setPicture(chatid, media); + }, this.id._serialized, media); + + return success; + } + /** * Gets the invite code for a specific group * @returns {Promise&lt;string>} Group's invite code */ async getInviteCode() { - const code = await this.client.pupPage.evaluate(async chatId => { + const codeRes = await this.client.pupPage.evaluate(async chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.Invite.sendQueryGroupInviteCode(chatWid); + return window.Store.GroupInvite.queryGroupInviteCode(chatWid); }, this.id._serialized); - return code; + return codeRes.code; } /** @@ -241,12 +401,63 @@

Source: structures/GroupChat.js

* @returns {Promise&lt;string>} New invite code */ async revokeInvite() { - const code = await this.client.pupPage.evaluate(chatId => { + const codeRes = await this.client.pupPage.evaluate(chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.Invite.sendRevokeGroupInviteCode(chatWid); + return window.Store.GroupInvite.resetGroupInviteCode(chatWid); }, this.id._serialized); - return code; + return codeRes.code; + } + + /** + * An object that handles the information about the group membership request + * @typedef {Object} GroupMembershipRequest + * @property {Object} id The wid of a user who requests to enter the group + * @property {Object} addedBy The wid of a user who created that request + * @property {Object|null} parentGroupId The wid of a community parent group to which the current group is linked + * @property {string} requestMethod The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin + * @property {number} t The timestamp the request was created at + */ + + /** + * Gets an array of membership requests + * @returns {Promise&lt;Array&lt;GroupMembershipRequest>>} An array of membership requests + */ + async getGroupMembershipRequests() { + return await this.client.getGroupMembershipRequests(this.id._serialized); + } + + /** + * An object that handles the result for membership request action + * @typedef {Object} MembershipRequestActionResult + * @property {string} requesterId User ID whos membership request was approved/rejected + * @property {number} error An error code that occurred during the operation for the participant + * @property {string} message A message with a result of membership request action + */ + + /** + * An object that handles options for {@link approveGroupMembershipRequests} and {@link rejectGroupMembershipRequests} methods + * @typedef {Object} MembershipRequestActionOptions + * @property {Array&lt;string>|string|null} requesterIds User ID/s who requested to join the group, if no value is provided, the method will search for all membership requests for that group + * @property {Array&lt;number>|number|null} sleep The number of milliseconds to wait before performing an operation for the next requester. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500] + */ + + /** + * Approves membership requests if any + * @param {MembershipRequestActionOptions} options Options for performing a membership request action + * @returns {Promise&lt;Array&lt;MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned + */ + async approveGroupMembershipRequests(options = {}) { + return await this.client.approveGroupMembershipRequests(this.id._serialized, options); + } + + /** + * Rejects membership requests if any + * @param {MembershipRequestActionOptions} options Options for performing a membership request action + * @returns {Promise&lt;Array&lt;MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned + */ + async rejectGroupMembershipRequests(options = {}) { + return await this.client.rejectGroupMembershipRequests(this.id._serialized, options); } /** @@ -254,15 +465,17 @@

Source: structures/GroupChat.js

* @returns {Promise} */ async leave() { - await this.client.pupPage.evaluate(chatId => { + await this.client.pupPage.evaluate(async chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.GroupUtils.sendExitGroup(chatWid); + const chat = await window.Store.Chat.find(chatWid); + return window.Store.GroupUtils.sendExitGroup(chat); }, this.id._serialized); } } -module.exports = GroupChat; +module.exports = GroupChat; + @@ -272,7 +485,7 @@

Source: structures/GroupChat.js

diff --git a/docs/structures_GroupNotification.js.html b/docs/structures_GroupNotification.js.html index 943eb50c61..9e7113921a 100644 --- a/docs/structures_GroupNotification.js.html +++ b/docs/structures_GroupNotification.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/GroupNotification.js + whatsapp-web.js 1.23.0 » Source: structures/GroupNotification.js @@ -15,7 +15,7 @@ @@ -143,7 +143,7 @@

Source: structures/GroupNotification.js

diff --git a/docs/structures_Label.js.html b/docs/structures_Label.js.html index 1a28b122a0..a0df02ac97 100644 --- a/docs/structures_Label.js.html +++ b/docs/structures_Label.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Label.js + whatsapp-web.js 1.23.0 » Source: structures/Label.js @@ -15,7 +15,7 @@ @@ -88,7 +88,7 @@

Source: structures/Label.js

diff --git a/docs/structures_List.js.html b/docs/structures_List.js.html index 5f66e7af0b..a6e5e511ae 100644 --- a/docs/structures_List.js.html +++ b/docs/structures_List.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/List.js + whatsapp-web.js 1.23.0 » Source: structures/List.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@

Source: structures/List.js

diff --git a/docs/structures_Location.js.html b/docs/structures_Location.js.html index 1dfb302f4b..c56cb78d8b 100644 --- a/docs/structures_Location.js.html +++ b/docs/structures_Location.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Location.js + whatsapp-web.js 1.23.0 » Source: structures/Location.js @@ -15,7 +15,7 @@ @@ -31,6 +31,14 @@

Source: structures/Location.js

'use strict';
 
+/**
+ * Location send options
+ * @typedef {Object} LocationSendOptions
+ * @property {string} [name] Location name
+ * @property {string} [address] Location address
+ * @property {string} [url] URL address to be shown within a location message
+ */
+
 /**
  * Location information
  */
@@ -38,9 +46,9 @@ 

Source: structures/Location.js

/** * @param {number} latitude * @param {number} longitude - * @param {?string} description + * @param {LocationSendOptions} [options] Location send options */ - constructor(latitude, longitude, description) { + constructor(latitude, longitude, options = {}) { /** * Location latitude * @type {number} @@ -55,9 +63,29 @@

Source: structures/Location.js

/** * Name for the location - * @type {?string} + * @type {string|undefined} + */ + this.name = options.name; + + /** + * Location address + * @type {string|undefined} + */ + this.address = options.address; + + /** + * URL address to be shown within a location message + * @type {string|undefined} + */ + this.url = options.url; + + /** + * Location full description + * @type {string|undefined} */ - this.description = description; + this.description = this.name &amp;&amp; this.address + ? `${this.name}\n${this.address}` + : this.name || this.address || ''; } } @@ -71,7 +99,7 @@

Source: structures/Location.js

diff --git a/docs/structures_Message.js.html b/docs/structures_Message.js.html index d937dd51e2..6b1b4bbc6e 100644 --- a/docs/structures_Message.js.html +++ b/docs/structures_Message.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Message.js + whatsapp-web.js 1.23.0 » Source: structures/Message.js @@ -15,7 +15,7 @@ @@ -36,7 +36,9 @@

Source: structures/Message.js

const Location = require('./Location'); const Order = require('./Order'); const Payment = require('./Payment'); -const { MessageTypes } = require('../util/Constants'); +const Reaction = require('./Reaction'); +const {MessageTypes} = require('../util/Constants'); +const {Contact} = require('./Contact'); /** * Represents a Message on WhatsApp @@ -80,7 +82,7 @@

Source: structures/Message.js

* Message content * @type {string} */ - this.body = this.hasMedia ? data.caption || '' : data.body || ''; + this.body = this.hasMedia ? data.caption || '' : data.body || data.pollName || ''; /** * Message type @@ -119,8 +121,7 @@

Source: structures/Message.js

* 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 = typeof data.id.id === 'string' &amp;&amp; data.id.id.length > 21 ? 'android' : typeof data.id.id === 'string' &amp;&amp; data.id.id.substring(0, 2) === '3A' ? 'ios' : 'web'; /** * Indicates if the message was forwarded * @type {boolean} @@ -139,7 +140,7 @@

Source: structures/Message.js

* Indicates if the message is a status update * @type {boolean} */ - this.isStatus = data.isStatusV3; + this.isStatus = data.isStatusV3 || data.id.remote === 'status@broadcast'; /** * Indicates if the message was starred @@ -165,6 +166,12 @@

Source: structures/Message.js

*/ this.hasQuotedMsg = data.quotedMsg ? true : false; + /** + * Indicates whether there are reactions to the message + * @type {boolean} + */ + this.hasReaction = data.hasReaction ? true : false; + /** * Indicates the duration of the message in seconds * @type {string} @@ -175,7 +182,21 @@

Source: structures/Message.js

* Location information contained in the message, if the message is type "location" * @type {Location} */ - this.location = data.type === MessageTypes.LOCATION ? new Location(data.lat, data.lng, data.loc) : undefined; + this.location = (() => { + if (data.type !== MessageTypes.LOCATION) { + return undefined; + } + let description; + if (data.loc &amp;&amp; typeof data.loc === 'string') { + let splitted = data.loc.split('\n'); + description = { + name: splitted[0], + address: splitted[1], + url: data.clientUrl + }; + } + return new Location(data.lat, data.lng, description); + })(); /** * List of vCards contained in the message. @@ -192,8 +213,8 @@

Source: structures/Message.js

inviteCodeExp: data.inviteCodeExp, groupId: data.inviteGrp, groupName: data.inviteGrpName, - fromId: data.from._serialized, - toId: data.to._serialized + fromId: '_serialized' in data.from ? data.from._serialized : data.from, + toId: '_serialized' in data.to ? data.to._serialized : data.to } : undefined; /** @@ -249,6 +270,16 @@

Source: structures/Message.js

this.productId = data.productId; } + /** Last edit time */ + if (data.latestEditSenderTimestampMs) { + this.latestEditSenderTimestampMs = data.latestEditSenderTimestampMs; + } + + /** Last edit message author */ + if (data.latestEditMsgKey) { + this.latestEditMsgKey = data.latestEditMsgKey; + } + /** * Links included in the message. * @type {Array&lt;{link: string, isSuspicious: boolean}>} @@ -271,6 +302,20 @@

Source: structures/Message.js

this.selectedRowId = data.listResponse.singleSelectReply.selectedRowId; } + if (this.type === MessageTypes.POLL_CREATION) { + this.pollName = data.pollName; + this.pollOptions = data.pollOptions; + this.allowMultipleAnswers = Boolean(!data.pollSelectableOptionsCount); + this.pollInvalidated = data.pollInvalidated; + this.isSentCagPollCreation = data.isSentCagPollCreation; + + delete this._data.pollName; + delete this._data.pollOptions; + delete this._data.pollSelectableOptionsCount; + delete this._data.pollInvalidated; + delete this._data.isSentCagPollCreation; + } + return super._patch(data); } @@ -336,8 +381,9 @@

Source: structures/Message.js

if (!this.hasQuotedMsg) return undefined; const quotedMsg = await this.client.pupPage.evaluate((msgId) => { - let msg = window.Store.Msg.get(msgId); - return msg.quotedMsgObj().serialize(); + const msg = window.Store.Msg.get(msgId); + const quotedMsg = window.Store.QuotedMsg.getQuotedMsgObj(msg); + return window.WWebJS.getMessageModel(quotedMsg); }, this.id._serialized); return new Message(this.client, quotedMsg); @@ -373,6 +419,8 @@

Source: structures/Message.js

*/ async react(reaction){ await this.client.pupPage.evaluate(async (messageId, reaction) => { + if (!messageId) { return undefined; } + const msg = await window.Store.Msg.get(messageId); await window.Store.sendReactionToMsg(msg, reaction); }, this.id._serialized, reaction); @@ -387,7 +435,7 @@

Source: structures/Message.js

} /** - * Forwards this message to another chat + * Forwards this message to another chat (that you chatted before, otherwise it will fail) * * @param {string|Chat} chat Chat model or chat ID to which the message will be forwarded * @returns {Promise} @@ -414,7 +462,9 @@

Source: structures/Message.js

const result = await this.client.pupPage.evaluate(async (msgId) => { const msg = window.Store.Msg.get(msgId); - + if (!msg) { + return undefined; + } if (msg.mediaData.mediaStage != 'RESOLVED') { // try to resolve media await msg.downloadMedia({ @@ -429,7 +479,7 @@

Source: structures/Message.js

} try { - const decryptedMedia = await window.Store.DownloadManager.downloadAndDecrypt({ + const decryptedMedia = await window.Store.DownloadManager.downloadAndMaybeDecrypt({ directPath: msg.directPath, encFilehash: msg.encFilehash, filehash: msg.filehash, @@ -439,12 +489,13 @@

Source: structures/Message.js

signal: (new AbortController).signal }); - const data = window.WWebJS.arrayBufferToBase64(decryptedMedia); + const data = await window.WWebJS.arrayBufferToBase64Async(decryptedMedia); return { data, mimetype: msg.mimetype, - filename: msg.filename + filename: msg.filename, + filesize: msg.size }; } catch (e) { if(e.status &amp;&amp; e.status === 404) return undefined; @@ -453,22 +504,24 @@

Source: structures/Message.js

}, this.id._serialized); if (!result) return undefined; - return new MessageMedia(result.mimetype, result.data, result.filename); + return new MessageMedia(result.mimetype, result.data, result.filename, result.filesize); } /** * Deletes a message from the chat - * @param {?boolean} everyone If true and the message is sent by the current user, will delete it for everyone in the chat. + * @param {?boolean} everyone If true and the message is sent by the current user or the user is an admin, will delete it for everyone in the chat. */ async delete(everyone) { - await this.client.pupPage.evaluate((msgId, everyone) => { + await this.client.pupPage.evaluate(async (msgId, everyone) => { 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], {type: 'Sender'}); + let chat = await window.Store.Chat.find(msg.id.remote); + + const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg); + if (everyone &amp;&amp; canRevoke) { + return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); } - return window.Store.Cmd.sendDeleteMsgs(msg.chat, [msg], true); + return window.Store.Cmd.sendDeleteMsgs(chat, [msg], true); }, this.id._serialized, everyone); } @@ -476,11 +529,12 @@

Source: structures/Message.js

* Stars this message */ async star() { - await this.client.pupPage.evaluate((msgId) => { + await this.client.pupPage.evaluate(async (msgId) => { let msg = window.Store.Msg.get(msgId); - - if (msg.canStar()) { - return window.Store.Cmd.sendStarMsgs(msg.chat, [msg], false); + + if (window.Store.MsgActionChecks.canStarMsg(msg)) { + let chat = await window.Store.Chat.find(msg.id.remote); + return window.Store.Cmd.sendStarMsgs(chat, [msg], false); } }, this.id._serialized); } @@ -489,11 +543,12 @@

Source: structures/Message.js

* Unstars this message */ async unstar() { - await this.client.pupPage.evaluate((msgId) => { + await this.client.pupPage.evaluate(async (msgId) => { let msg = window.Store.Msg.get(msgId); - if (msg.canStar()) { - return window.Store.Cmd.sendUnstarMsgs(msg.chat, [msg], false); + if (window.Store.MsgActionChecks.canStarMsg(msg)) { + let chat = await window.Store.Chat.find(msg.id.remote); + return window.Store.Cmd.sendUnstarMsgs(chat, [msg], false); } }, this.id._serialized); } @@ -518,7 +573,7 @@

Source: structures/Message.js

const msg = window.Store.Msg.get(msgId); if (!msg) return null; - return await window.Store.MessageInfo.sendQueryMsgInfo(msg); + return await window.Store.MessageInfo.sendQueryMsgInfo(msg.id); }, this.id._serialized); return info; @@ -553,6 +608,80 @@

Source: structures/Message.js

} return undefined; } + + + /** + * Reaction List + * @typedef {Object} ReactionList + * @property {string} id Original emoji + * @property {string} aggregateEmoji aggregate emoji + * @property {boolean} hasReactionByMe Flag who sent the reaction + * @property {Array&lt;Reaction>} senders Reaction senders, to this message + */ + + /** + * Gets the reactions associated with the given message + * @return {Promise&lt;ReactionList[]>} + */ + async getReactions() { + if (!this.hasReaction) { + return undefined; + } + + const reactions = await this.client.pupPage.evaluate(async (msgId) => { + const msgReactions = await window.Store.Reactions.find(msgId); + if (!msgReactions || !msgReactions.reactions.length) return null; + return msgReactions.reactions.serialize(); + }, this.id._serialized); + + if (!reactions) { + return undefined; + } + + return reactions.map(reaction => { + reaction.senders = reaction.senders.map(sender => { + sender.timestamp = Math.round(sender.timestamp / 1000); + return new Reaction(this.client, sender); + }); + return reaction; + }); + } + + /** + * Edits the current message. + * @param {string} content + * @param {MessageEditOptions} [options] - Options used when editing the message + * @returns {Promise&lt;?Message>} + */ + async edit(content, options = {}) { + if (options.mentions &amp;&amp; options.mentions.some(possiblyContact => possiblyContact instanceof Contact)) { + options.mentions = options.mentions.map(a => a.id._serialized); + } + let internalOptions = { + linkPreview: options.linkPreview === false ? undefined : true, + mentionedJidList: Array.isArray(options.mentions) ? options.mentions : [], + extraOptions: options.extra + }; + + if (!this.fromMe) { + return null; + } + const messageEdit = await this.client.pupPage.evaluate(async (msgId, message, options) => { + let msg = window.Store.Msg.get(msgId); + if (!msg) return null; + + let catEdit = (msg.type === 'chat' &amp;&amp; window.Store.MsgActionChecks.canEditText(msg)); + if (catEdit) { + const msgEdit = await window.WWebJS.editMessage(msg, message, options); + return msgEdit.serialize(); + } + return null; + }, this.id._serialized, content, internalOptions); + if (messageEdit) { + return new Message(this.client, messageEdit); + } + return null; + } } module.exports = Message; @@ -566,7 +695,7 @@

Source: structures/Message.js

diff --git a/docs/structures_MessageMedia.js.html b/docs/structures_MessageMedia.js.html index f1886fa596..60a12484d0 100644 --- a/docs/structures_MessageMedia.js.html +++ b/docs/structures_MessageMedia.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/MessageMedia.js + whatsapp-web.js 1.23.0 » Source: structures/MessageMedia.js @@ -15,7 +15,7 @@ @@ -41,10 +41,11 @@

Source: structures/MessageMedia.js

* Media attached to a message * @param {string} mimetype MIME type of the attachment * @param {string} data Base64-encoded data of the file - * @param {?string} filename Document file name + * @param {?string} filename Document file name. Value can be null + * @param {?number} filesize Document file size in bytes. Value can be null */ class MessageMedia { - constructor(mimetype, data, filename) { + constructor(mimetype, data, filename, filesize) { /** * MIME type of the attachment * @type {string} @@ -58,10 +59,16 @@

Source: structures/MessageMedia.js

this.data = data; /** - * Name of the file (for documents) + * Document file name. Value can be null * @type {?string} */ this.filename = filename; + + /** + * Document file size in bytes. Value can be null + * @type {?number} + */ + this.filesize = filesize; } /** @@ -99,6 +106,7 @@

Source: structures/MessageMedia.js

const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options); const response = await fetch(url, reqOptions); const mime = response.headers.get('Content-Type'); + const size = response.headers.get('Content-Length'); const contentDisposition = response.headers.get('Content-Disposition'); const name = contentDisposition ? contentDisposition.match(/((?&lt;=filename=")(.*)(?="))/) : null; @@ -114,7 +122,7 @@

Source: structures/MessageMedia.js

data = btoa(data); } - return { data, mime, name }; + return { data, mime, name, size }; } const res = options.client @@ -127,7 +135,7 @@

Source: structures/MessageMedia.js

if (!mimetype) mimetype = res.mime; - return new MessageMedia(mimetype, res.data, filename); + return new MessageMedia(mimetype, res.data, filename, res.size || null); } } @@ -142,7 +150,7 @@

Source: structures/MessageMedia.js

diff --git a/docs/structures_Order.js.html b/docs/structures_Order.js.html index 26461b3ab3..cb091f3992 100644 --- a/docs/structures_Order.js.html +++ b/docs/structures_Order.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Order.js + whatsapp-web.js 1.23.0 » Source: structures/Order.js @@ -15,7 +15,7 @@ @@ -90,7 +90,7 @@

Source: structures/Order.js

diff --git a/docs/structures_Payment.js.html b/docs/structures_Payment.js.html index 8e74292ae0..f4598b1b8b 100644 --- a/docs/structures_Payment.js.html +++ b/docs/structures_Payment.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Payment.js + whatsapp-web.js 1.23.0 » Source: structures/Payment.js @@ -15,7 +15,7 @@ @@ -118,7 +118,7 @@

Source: structures/Payment.js

diff --git a/docs/structures_Poll.js.html b/docs/structures_Poll.js.html new file mode 100644 index 0000000000..da5c481702 --- /dev/null +++ b/docs/structures_Poll.js.html @@ -0,0 +1,98 @@ + + + + + + + whatsapp-web.js 1.23.0 » Source: structures/Poll.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+/**
+ * Poll send options
+ * @typedef {Object} PollSendOptions
+ * @property {boolean} [allowMultipleAnswers=false] If false it is a single choice poll, otherwise it is a multiple choice poll (false by default)
+ * @property {?Array&lt;number>} messageSecret The custom message secret, can be used as a poll ID. NOTE: it has to be a unique vector with a length of 32
+ */
+
+/** Represents a Poll on WhatsApp */
+class Poll {
+    /**
+     * @param {string} pollName
+     * @param {Array&lt;string>} pollOptions
+     * @param {PollSendOptions} options
+     */
+    constructor(pollName, pollOptions, options = {}) {
+        /**
+         * The name of the poll
+         * @type {string}
+         */
+        this.pollName = pollName.trim();
+
+        /**
+         * The array of poll options
+         * @type {Array.&lt;{name: string, localId: number}>}
+         */
+        this.pollOptions = pollOptions.map((option, index) => ({
+            name: option.trim(),
+            localId: index
+        }));
+
+        /**
+         * The send options for the poll
+         * @type {PollSendOptions}
+         */
+        this.options = {
+            allowMultipleAnswers: options.allowMultipleAnswers === true,
+            messageSecret: options.messageSecret
+        };
+    }
+}
+
+module.exports = Poll;
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/structures_PrivateChat.js.html b/docs/structures_PrivateChat.js.html index 3f32888a7d..e337bd5958 100644 --- a/docs/structures_PrivateChat.js.html +++ b/docs/structures_PrivateChat.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/PrivateChat.js + whatsapp-web.js 1.23.0 » Source: structures/PrivateChat.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@

Source: structures/PrivateChat.js

diff --git a/docs/structures_PrivateContact.js.html b/docs/structures_PrivateContact.js.html index 40706a43f5..5017b0f4c7 100644 --- a/docs/structures_PrivateContact.js.html +++ b/docs/structures_PrivateContact.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/PrivateContact.js + whatsapp-web.js 1.23.0 » Source: structures/PrivateContact.js @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@

Source: structures/PrivateContact.js

diff --git a/docs/structures_Product.js.html b/docs/structures_Product.js.html index 0c77d311ad..a5e898f06a 100644 --- a/docs/structures_Product.js.html +++ b/docs/structures_Product.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/Product.js + whatsapp-web.js 1.23.0 » Source: structures/Product.js @@ -15,7 +15,7 @@ @@ -106,7 +106,7 @@

Source: structures/Product.js

diff --git a/docs/structures_ProductMetadata.js.html b/docs/structures_ProductMetadata.js.html index 220bef0210..c0c38ad6a1 100644 --- a/docs/structures_ProductMetadata.js.html +++ b/docs/structures_ProductMetadata.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: structures/ProductMetadata.js + whatsapp-web.js 1.23.0 » Source: structures/ProductMetadata.js @@ -15,7 +15,7 @@ @@ -63,7 +63,7 @@

Source: structures/ProductMetadata.js

diff --git a/docs/structures_Reaction.js.html b/docs/structures_Reaction.js.html new file mode 100644 index 0000000000..a2ac667564 --- /dev/null +++ b/docs/structures_Reaction.js.html @@ -0,0 +1,122 @@ + + + + + + + whatsapp-web.js 1.23.0 » Source: structures/Reaction.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+const Base = require('./Base');
+
+/**
+ * Represents a Reaction on WhatsApp
+ * @extends {Base}
+ */
+class Reaction extends Base {
+    constructor(client, data) {
+        super(client);
+
+        if (data) this._patch(data);
+    }
+
+    _patch(data) {
+        /**
+         * Reaction ID
+         * @type {object}
+         */
+        this.id = data.msgKey;
+        /**
+         * Orphan
+         * @type {number}
+         */
+        this.orphan = data.orphan;
+        /**
+         * Orphan reason
+         * @type {?string}
+         */
+        this.orphanReason = data.orphanReason;
+        /**
+         * Unix timestamp for when the reaction was created
+         * @type {number}
+         */
+        this.timestamp = data.timestamp;
+        /**
+         * Reaction
+         * @type {string}
+         */
+        this.reaction = data.reactionText;
+        /**
+         * Read
+         * @type {boolean}
+         */
+        this.read = data.read;
+        /**
+         * Message ID
+         * @type {object}
+         */
+        this.msgId = data.parentMsgKey;
+        /**
+         * Sender ID
+         * @type {string}
+         */
+        this.senderId = data.senderUserJid;
+        /**
+         * ACK
+         * @type {?number}
+         */
+        this.ack = data.ack;
+        
+        
+        return super._patch(data);
+    }
+    
+}
+
+module.exports = Reaction;
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/util_Constants.js.html b/docs/util_Constants.js.html index ab0edca504..596f819f5e 100644 --- a/docs/util_Constants.js.html +++ b/docs/util_Constants.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: util/Constants.js + whatsapp-web.js 1.23.0 » Source: util/Constants.js @@ -15,7 +15,7 @@ @@ -38,13 +38,18 @@

Source: util/Constants.js

headless: true, defaultViewport: null }, + webVersion: '2.2346.52', + webVersionCache: { + type: 'local', + }, authTimeoutMs: 0, qrMaxRetries: 0, takeoverOnConflict: false, takeoverTimeoutMs: 0, - userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36', + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36', ffmpegPath: 'ffmpeg', - bypassCSP: false + bypassCSP: false, + proxyAuthentication: undefined }; /** @@ -67,20 +72,30 @@

Source: util/Constants.js

AUTHENTICATED: 'authenticated', AUTHENTICATION_FAILURE: 'auth_failure', READY: 'ready', + CHAT_REMOVED: 'chat_removed', + CHAT_ARCHIVED: 'chat_archived', MESSAGE_RECEIVED: 'message', MESSAGE_CREATE: 'message_create', MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone', MESSAGE_REVOKED_ME: 'message_revoke_me', MESSAGE_ACK: 'message_ack', + MESSAGE_EDIT: 'message_edit', + UNREAD_COUNT: 'unread_count', + MESSAGE_REACTION: 'message_reaction', MEDIA_UPLOADED: 'media_uploaded', + CONTACT_CHANGED: 'contact_changed', GROUP_JOIN: 'group_join', GROUP_LEAVE: 'group_leave', + GROUP_ADMIN_CHANGED: 'group_admin_changed', + GROUP_MEMBERSHIP_REQUEST: 'group_membership_request', GROUP_UPDATE: 'group_update', QR_RECEIVED: 'qr', + LOADING_SCREEN: 'loading_screen', DISCONNECTED: 'disconnected', STATE_CHANGED: 'change_state', BATTERY_CHANGED: 'change_battery', - INCOMING_CALL: 'incoming_call' + INCOMING_CALL: 'call', + REMOTE_SESSION_SAVED: 'remote_session_saved' }; /** @@ -124,6 +139,7 @@

Source: util/Constants.js

PROTOCOL: 'protocol', REACTION: 'reaction', TEMPLATE_BUTTON_REPLY: 'template_button_reply', + POLL_CREATION: 'poll_creation', }; /** @@ -197,7 +213,7 @@

Source: util/Constants.js

diff --git a/docs/util_Injected.js.html b/docs/util_Injected.js.html new file mode 100644 index 0000000000..3e1a0881b2 --- /dev/null +++ b/docs/util_Injected.js.html @@ -0,0 +1,1148 @@ + + + + + + + whatsapp-web.js 1.23.0 » Source: util/Injected.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+// Exposes the internal Store to the WhatsApp Web client
+exports.ExposeStore = (moduleRaidStr) => {
+    eval('var moduleRaid = ' + moduleRaidStr);
+    // eslint-disable-next-line no-undef
+    window.mR = moduleRaid();
+    window.Store = Object.assign({}, window.mR.findModule(m => m.default &amp;&amp; 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.BlockContact = window.mR.findModule('blockContact')[0];
+    window.Store.Call = window.mR.findModule((module) => module.default &amp;&amp; module.default.Call)[0].default.Call;
+    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.GroupMetadata = window.mR.findModule('GroupMetadata')[0].default.GroupMetadata;
+    window.Store.GroupMetadata.queryAndUpdate = window.mR.findModule('queryAndUpdateGroupMetadataById')[0].queryAndUpdateGroupMetadataById;
+    window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection;
+    window.Store.ContactCollection = window.mR.findModule('ContactCollection')[0].ContactCollection;
+    window.Store.MediaPrep = window.mR.findModule('prepRawMedia')[0];
+    window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0];
+    window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0];
+    window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0];
+    window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0];
+    window.Store.MsgKey = window.mR.findModule((module) => module.default &amp;&amp; module.default.fromString)[0].default;
+    window.Store.MessageInfo = window.mR.findModule('sendQueryMsgInfo')[0];
+    window.Store.OpaqueData = window.mR.findModule(module => module.default &amp;&amp; module.default.createFromData)[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.EditMessage = window.mR.findModule('addAndSendMessageEdit')[0];
+    window.Store.SendSeen = window.mR.findModule('sendSeen')[0];
+    window.Store.User = window.mR.findModule('getMaybeMeUser')[0];
+    window.Store.ContactMethods = window.mR.findModule('getUserid')[0];
+    window.Store.BusinessProfileCollection = window.mR.findModule('BusinessProfileCollection')[0].BusinessProfileCollection;
+    window.Store.UploadUtils = window.mR.findModule((module) => (module.default &amp;&amp; module.default.encryptAndUpload) ? module.default : null)[0].default;
+    window.Store.UserConstructor = window.mR.findModule((module) => (module.default &amp;&amp; module.default.prototype &amp;&amp; module.default.prototype.isServer &amp;&amp; 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.WidFactory = window.mR.findModule('createWid')[0];
+    window.Store.ProfilePic = window.mR.findModule('profilePicResync')[0];
+    window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0];
+    window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0];
+    window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups;
+    window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0];
+    window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0];
+    window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg;
+    window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0];
+    window.Store.EphemeralFields = window.mR.findModule('getEphemeralFields')[0];
+    window.Store.MsgActionChecks = window.mR.findModule('canSenderRevokeMsg')[0];
+    window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0];
+    window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0];
+    window.Store.SocketWap = window.mR.findModule('wap')[0];
+    window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext;
+    window.Store.DrawerManager = window.mR.findModule('DrawerManager')[0].DrawerManager;
+    window.Store.LidUtils = window.mR.findModule('getCurrentLid')[0];
+    window.Store.WidToJid = window.mR.findModule('widToUserJid')[0];
+    window.Store.JidToWid = window.mR.findModule('userJidToUserWid')[0];
+    
+    /* eslint-disable no-undef, no-cond-assign */
+    window.Store.QueryExist = ((m = window.mR.findModule('queryExists')[0]) ? m.queryExists : window.mR.findModule('queryExist')[0].queryWidExists);
+    window.Store.ReplyUtils = (m = window.mR.findModule('canReplyMsg')).length > 0 &amp;&amp; m[0];
+    /* eslint-enable no-undef, no-cond-assign */
+
+    window.Store.StickerTools = {
+        ...window.mR.findModule('toWebpSticker')[0],
+        ...window.mR.findModule('addWebpMetadata')[0]
+    };
+    window.Store.GroupUtils = {
+        ...window.mR.findModule('createGroup')[0],
+        ...window.mR.findModule('setGroupDescription')[0],
+        ...window.mR.findModule('sendExitGroup')[0],
+        ...window.mR.findModule('sendSetPicture')[0]
+    };
+    window.Store.GroupParticipants = {
+        ...window.mR.findModule('promoteParticipants')[0],
+        ...window.mR.findModule('sendAddParticipantsRPC')[0]
+    };
+    window.Store.GroupInvite = {
+        ...window.mR.findModule('resetGroupInviteCode')[0],
+        ...window.mR.findModule('queryGroupInvite')[0]
+    };
+    window.Store.GroupInviteV4 = {
+        ...window.mR.findModule('queryGroupInviteV4')[0],
+        ...window.mR.findModule('sendGroupInviteMessage')[0]
+    };
+    window.Store.MembershipRequestUtils = {
+        ...window.mR.findModule('getMembershipApprovalRequests')[0],
+        ...window.mR.findModule('sendMembershipRequestsActionRPC')[0]
+    };
+
+    if (!window.Store.Chat._find) {
+        window.Store.Chat._find = e => {
+            const target = window.Store.Chat.get(e);
+            return target ? Promise.resolve(target) : Promise.resolve({
+                id: e
+            });
+        };
+    }
+    
+    // eslint-disable-next-line no-undef
+    if ((m = window.mR.findModule('ChatCollection')[0]) &amp;&amp; m.ChatCollection &amp;&amp; typeof m.ChatCollection.findImpl === 'undefined' &amp;&amp; typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find;
+
+    // TODO remove these once everybody has been updated to WWebJS with legacy sessions removed
+    const _linkPreview = window.mR.findModule('queryLinkPreview');
+    if (_linkPreview &amp;&amp; _linkPreview[0] &amp;&amp; _linkPreview[0].default) {
+        window.Store.Wap = _linkPreview[0].default;
+    }
+
+    const _isMDBackend = window.mR.findModule('isMDBackend');
+    if(_isMDBackend &amp;&amp; _isMDBackend[0] &amp;&amp; _isMDBackend[0].isMDBackend) {
+        window.Store.MDBackend = _isMDBackend[0].isMDBackend();
+    } else {
+        window.Store.MDBackend = true;
+    }
+
+    const _features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0];
+    if(_features) {
+        window.Store.Features = _features.LegacyPhoneFeatures;
+    }
+
+    /**
+     * Target options object description
+     * @typedef {Object} TargetOptions
+     * @property {string|number} moduleId The name or a key of the target module to search
+     * @property {number} index The index value of the target module
+     * @property {string} property The function name to get from a module
+     */
+
+    /**
+     * Function to modify functions
+     * @param {TargetOptions} target Options specifying the target function to search for modifying
+     * @param {Function} callback Modified function
+     */
+    window.injectToFunction = (target, callback) => {
+        const module = typeof target.moduleId === 'string'
+            ? window.mR.findModule(target.moduleId)
+            : window.mR.modules[target.moduleId];
+        const originalFunction = module[target.index][target.property];
+        const modifiedFunction = (...args) => callback(originalFunction, ...args);
+        module[target.index][target.property] = modifiedFunction;
+    };
+
+    window.injectToFunction({ moduleId: 'mediaTypeFromProtobuf', index: 0, property: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });
+
+    window.injectToFunction({ moduleId: 'typeAttributeFromProtobuf', index: 0, property: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });
+};
+
+exports.LoadUtils = () => {
+    window.WWebJS = {};
+
+    window.WWebJS.sendSeen = async (chatId) => {
+        let chat = window.Store.Chat.get(chatId);
+        if (chat !== undefined) {
+            await window.Store.SendSeen.sendSeen(chat, false);
+            return true;
+        }
+        return false;
+
+    };
+
+    window.WWebJS.sendMessage = async (chat, content, options = {}) => {
+        let attOptions = {};
+        if (options.attachment) {
+            attOptions = options.sendMediaAsSticker
+                ? await window.WWebJS.processStickerData(options.attachment)
+                : await window.WWebJS.processMediaData(options.attachment, {
+                    forceVoice: options.sendAudioAsVoice,
+                    forceDocument: options.sendMediaAsDocument,
+                    forceGif: options.sendVideoAsGif
+                });
+            
+            if (options.caption){
+                attOptions.caption = options.caption; 
+            }
+            content = options.sendMediaAsSticker ? undefined : attOptions.preview;
+            attOptions.isViewOnce = options.isViewOnce;
+
+            delete options.attachment;
+            delete options.sendMediaAsSticker;
+        }
+        let quotedMsgOptions = {};
+        if (options.quotedMessageId) {
+            let quotedMessage = window.Store.Msg.get(options.quotedMessageId);
+
+            // TODO remove .canReply() once all clients are updated to >= v2.2241.6
+            const canReply = window.Store.ReplyUtils ? 
+                window.Store.ReplyUtils.canReplyMsg(quotedMessage.unsafe()) : 
+                quotedMessage.canReply();
+
+            if (canReply) {
+                quotedMsgOptions = quotedMessage.msgContextInfo(chat);
+            }
+            delete options.quotedMessageId;
+        }
+
+        if (options.mentionedJidList) {
+            options.mentionedJidList = options.mentionedJidList.map(cId => window.Store.Contact.get(cId).id);
+        }
+
+        let locationOptions = {};
+        if (options.location) {
+            let { latitude, longitude, description, url } = options.location;
+            url = window.Store.Validators.findLink(url)?.href;
+            url &amp;&amp; !description &amp;&amp; (description = url);
+            locationOptions = {
+                type: 'location',
+                loc: description,
+                lat: latitude,
+                lng: longitude,
+                clientUrl: url
+            };
+            delete options.location;
+        }
+
+        let _pollOptions = {};
+        if (options.poll) {
+            const { pollName, pollOptions } = options.poll;
+            const { allowMultipleAnswers, messageSecret } = options.poll.options;
+            _pollOptions = {
+                type: 'poll_creation',
+                pollName: pollName,
+                pollOptions: pollOptions,
+                pollSelectableOptionsCount: allowMultipleAnswers ? 0 : 1,
+                messageSecret:
+                Array.isArray(messageSecret) &amp;&amp; messageSecret.length === 32
+                    ? new Uint8Array(messageSecret)
+                    : window.crypto.getRandomValues(new Uint8Array(32))
+            };
+            delete options.poll;
+        }
+
+        let vcardOptions = {};
+        if (options.contactCard) {
+            let contact = window.Store.Contact.get(options.contactCard);
+            vcardOptions = {
+                body: window.Store.VCard.vcardFromContactModel(contact).vcard,
+                type: 'vcard',
+                vcardFormattedName: contact.formattedName
+            };
+            delete options.contactCard;
+        } else if (options.contactCardList) {
+            let contacts = options.contactCardList.map(c => window.Store.Contact.get(c));
+            let vcards = contacts.map(c => window.Store.VCard.vcardFromContactModel(c));
+            vcardOptions = {
+                type: 'multi_vcard',
+                vcardList: vcards,
+                body: undefined
+            };
+            delete options.contactCardList;
+        } else if (options.parseVCards &amp;&amp; typeof (content) === 'string' &amp;&amp; content.startsWith('BEGIN:VCARD')) {
+            delete options.parseVCards;
+            try {
+                const parsed = window.Store.VCard.parseVcard(content);
+                if (parsed) {
+                    vcardOptions = {
+                        type: 'vcard',
+                        vcardFormattedName: window.Store.VCard.vcardGetNameFromParsed(parsed)
+                    };
+                }
+            } catch (_) {
+                // not a vcard
+            }
+        }
+
+        if (options.linkPreview) {
+            delete options.linkPreview;
+
+            // Not supported yet by WhatsApp Web on MD
+            if(!window.Store.MDBackend) {
+                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') {
+                content = options.buttons.body;
+                caption = content;
+            } else {
+                caption = options.caption ? options.caption : ' '; //Caption can't be empty
+            }
+            buttonOptions = {
+                productHeaderImageRejected: false,
+                isFromTemplate: false,
+                isDynamicReplyButtonsMsg: true,
+                title: options.buttons.title ? options.buttons.title : undefined,
+                footer: options.buttons.footer ? options.buttons.footer : undefined,
+                dynamicReplyButtons: options.buttons.buttons,
+                replyButtons: options.buttons.buttons,
+                caption: caption
+            };
+            delete options.buttons;
+        }
+
+        let listOptions = {};
+        if (options.list) {
+            if (window.Store.Conn.platform === 'smba' || window.Store.Conn.platform === 'smbi') {
+                throw '[LT01] Whatsapp business can\'t send this yet';
+            }
+            listOptions = {
+                type: 'list',
+                footer: options.list.footer,
+                list: {
+                    ...options.list,
+                    listType: 1
+                },
+                body: options.list.description
+            };
+            delete options.list;
+            delete listOptions.list.footer;
+        }
+
+        const meUser = window.Store.User.getMaybeMeUser();
+        const isMD = window.Store.MDBackend;
+        const newId = await window.Store.MsgKey.newId();
+        
+        const newMsgId = new window.Store.MsgKey({
+            from: meUser,
+            to: chat.id,
+            id: newId,
+            participant: isMD &amp;&amp; chat.id.isGroup() ? meUser : undefined,
+            selfDir: 'out',
+        });
+
+        const extraOptions = options.extraOptions || {};
+        delete options.extraOptions;
+
+        const ephemeralFields = window.Store.EphemeralFields.getEphemeralFields(chat);
+
+        const message = {
+            ...options,
+            id: newMsgId,
+            ack: 0,
+            body: content,
+            from: meUser,
+            to: chat.id,
+            local: true,
+            self: 'out',
+            t: parseInt(new Date().getTime() / 1000),
+            isNewMsg: true,
+            type: 'chat',
+            ...ephemeralFields,
+            ...locationOptions,
+            ..._pollOptions,
+            ...attOptions,
+            ...(attOptions.toJSON ? attOptions.toJSON() : {}),
+            ...quotedMsgOptions,
+            ...vcardOptions,
+            ...buttonOptions,
+            ...listOptions,
+            ...extraOptions
+        };
+
+        await window.Store.SendMessage.addAndSendMsgToChat(chat, message);
+        return window.Store.Msg.get(newMsgId._serialized);
+    };
+	
+    window.WWebJS.editMessage = async (msg, content, options = {}) => {
+
+        const extraOptions = options.extraOptions || {};
+        delete options.extraOptions;
+        
+        if (options.mentionedJidList) {
+            options.mentionedJidList = options.mentionedJidList.map(cId => window.Store.Contact.get(cId).id);
+        }
+
+        if (options.linkPreview) {
+            options.linkPreview = null;
+
+            // Not supported yet by WhatsApp Web on MD
+            if(!window.Store.MDBackend) {
+                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 };
+                }
+            }
+        }
+
+
+        const internalOptions = {
+            ...options,
+            ...extraOptions
+        };
+
+        await window.Store.EditMessage.sendMessageEdit(msg, content, internalOptions);
+        return window.Store.Msg.get(msg.id._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 mediaKey = await window.WWebJS.generateHash(32);
+
+        const controller = new AbortController();
+        const uploadedInfo = await window.Store.UploadUtils.encryptAndUpload({
+            blob: file,
+            type: 'sticker',
+            signal: controller.signal,
+            mediaKey
+        });
+
+        const stickerInfo = {
+            ...uploadedInfo,
+            clientUrl: uploadedInfo.url,
+            deprecatedMms3Url: uploadedInfo.url,
+            uploadhash: uploadedInfo.encFilehash,
+            size: file.size,
+            type: 'sticker',
+            filehash
+        };
+
+        return stickerInfo;
+    };
+
+    window.WWebJS.processMediaData = async (mediaInfo, { forceVoice, forceDocument, forceGif }) => {
+        const file = window.WWebJS.mediaInfoToFile(mediaInfo);
+        const mData = await window.Store.OpaqueData.createFromData(file, file.type);
+        const mediaPrep = window.Store.MediaPrep.prepRawMedia(mData, { asDocument: forceDocument });
+        const mediaData = await mediaPrep.waitForPrep();
+        const mediaObject = window.Store.MediaObject.getOrCreateMediaObject(mediaData.filehash);
+
+        const mediaType = window.Store.MediaTypes.msgToMediaType({
+            type: mediaData.type,
+            isGif: mediaData.isGif
+        });
+
+        if (forceVoice &amp;&amp; mediaData.type === 'audio') {
+            mediaData.type = 'ptt';
+            const waveform = mediaObject.contentInfo.waveform;
+            mediaData.waveform =
+                waveform ?? await window.WWebJS.generateWaveform(file);
+        }
+
+        if (forceGif &amp;&amp; mediaData.type === 'video') {
+            mediaData.isGif = true;
+        }
+
+        if (forceDocument) {
+            mediaData.type = 'document';
+        }
+
+        if (!(mediaData.mediaBlob instanceof window.Store.OpaqueData)) {
+            mediaData.mediaBlob = await window.Store.OpaqueData.createFromData(mediaData.mediaBlob, mediaData.mediaBlob.type);
+        }
+
+        mediaData.renderableUrl = mediaData.mediaBlob.url();
+        mediaObject.consolidate(mediaData.toJSON());
+        mediaData.mediaBlob.autorelease();
+
+        const uploadedMedia = await window.Store.MediaUpload.uploadMedia({
+            mimetype: mediaData.mimetype,
+            mediaObject,
+            mediaType
+        });
+
+        const mediaEntry = uploadedMedia.mediaEntry;
+        if (!mediaEntry) {
+            throw new Error('upload failed: media entry was not created');
+        }
+
+        mediaData.set({
+            clientUrl: mediaEntry.mmsUrl,
+            deprecatedMms3Url: mediaEntry.deprecatedMms3Url,
+            directPath: mediaEntry.directPath,
+            mediaKey: mediaEntry.mediaKey,
+            mediaKeyTimestamp: mediaEntry.mediaKeyTimestamp,
+            filehash: mediaObject.filehash,
+            encFilehash: mediaEntry.encFilehash,
+            uploadhash: mediaEntry.uploadHash,
+            size: mediaObject.size,
+            streamingSidecar: mediaEntry.sidecar,
+            firstFrameSidecar: mediaEntry.firstFrameSidecar
+        });
+
+        return mediaData;
+    };
+
+    window.WWebJS.getMessageModel = message => {
+        const msg = message.serialize();
+
+        msg.isEphemeral = message.isEphemeral;
+        msg.isStatusV3 = message.isStatusV3;
+        msg.links = (message.getRawLinks()).map(link => ({
+            link: link.href,
+            isSuspicious: Boolean(link.suspiciousCharacters &amp;&amp; link.suspiciousCharacters.size)
+        }));
+
+        if (msg.buttons) {
+            msg.buttons = msg.buttons.serialize();
+        }
+        if (msg.dynamicReplyButtons) {
+            msg.dynamicReplyButtons = JSON.parse(JSON.stringify(msg.dynamicReplyButtons));
+        }
+        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 });
+        }
+
+        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 &amp;&amp; chat.mute.isMuted;
+
+        if (chat.groupMetadata) {
+            const chatWid = window.Store.WidFactory.createWid((chat.id._serialized));
+            await window.Store.GroupMetadata.update(chatWid);
+            res.groupMetadata = chat.groupMetadata.serialize();
+        }
+        
+        res.lastMessage = null;
+        if (res.msgs &amp;&amp; res.msgs.length) {
+            const lastMessage = chat.lastReceivedKey ? window.Store.Msg.get(chat.lastReceivedKey._serialized) : null;
+            if (lastMessage) {
+                res.lastMessage = window.WWebJS.getMessageModel(lastMessage);
+            }
+        }
+        
+        delete res.msgs;
+        delete res.msgUnsyncedButtonReplyMsgs;
+        delete res.unsyncedButtonReplies;
+
+        return res;
+    };
+
+    window.WWebJS.getChat = async chatId => {
+        const chatWid = window.Store.WidFactory.createWid(chatId);
+        const chat = await window.Store.Chat.find(chatWid);
+        return await window.WWebJS.getChatModel(chat);
+    };
+
+    window.WWebJS.getChats = async () => {
+        const chats = window.Store.Chat.getModelsArray();
+
+        const chatPromises = chats.map(chat => window.WWebJS.getChatModel(chat));
+        return await Promise.all(chatPromises);
+    };
+
+    window.WWebJS.getContactModel = contact => {
+        let res = contact.serialize();
+        res.isBusiness = contact.isBusiness === undefined ? false : contact.isBusiness;
+
+        if (contact.businessProfile) {
+            res.businessProfile = contact.businessProfile.serialize();
+        }
+
+        // TODO: remove useOldImplementation and its checks once all clients are updated to >= v2.2327.4
+        const useOldImplementation
+            = window.compareWwebVersions(window.Debug.VERSION, '&lt;', '2.2327.4');
+
+        res.isMe = useOldImplementation
+            ? contact.isMe
+            : window.Store.ContactMethods.getIsMe(contact);
+        res.isUser = useOldImplementation
+            ? contact.isUser
+            : window.Store.ContactMethods.getIsUser(contact);
+        res.isGroup = useOldImplementation
+            ? contact.isGroup
+            : window.Store.ContactMethods.getIsGroup(contact);
+        res.isWAContact = useOldImplementation
+            ? contact.isWAContact
+            : window.Store.ContactMethods.getIsWAContact(contact);
+        res.isMyContact = useOldImplementation
+            ? contact.isMyContact
+            : window.Store.ContactMethods.getIsMyContact(contact);
+        res.isBlocked = contact.isContactBlocked;
+        res.userid = useOldImplementation
+            ? contact.userid
+            : window.Store.ContactMethods.getUserid(contact);
+        res.isEnterprise = useOldImplementation
+            ? contact.isEnterprise
+            : window.Store.ContactMethods.getIsEnterprise(contact);
+        res.verifiedName = useOldImplementation
+            ? contact.verifiedName
+            : window.Store.ContactMethods.getVerifiedName(contact);
+        res.verifiedLevel = useOldImplementation
+            ? contact.verifiedLevel
+            : window.Store.ContactMethods.getVerifiedLevel(contact);
+        res.statusMute = useOldImplementation
+            ? contact.statusMute
+            : window.Store.ContactMethods.getStatusMute(contact);
+        res.name = useOldImplementation
+            ? contact.name
+            : window.Store.ContactMethods.getName(contact);
+        res.shortName = useOldImplementation
+            ? contact.shortName
+            : window.Store.ContactMethods.getShortName(contact);
+        res.pushname = useOldImplementation
+            ? contact.pushname
+            : window.Store.ContactMethods.getPushname(contact);
+
+        return res;
+    };
+
+    window.WWebJS.getContact = async contactId => {
+        const wid = window.Store.WidFactory.createWid(contactId);
+        const contact = await window.Store.Contact.find(wid);
+        const bizProfile = await window.Store.BusinessProfileCollection.fetchBizProfile(wid);
+        bizProfile.profileOptions &amp;&amp; (contact.businessProfile = bizProfile);
+        return window.WWebJS.getContactModel(contact);
+    };
+
+    window.WWebJS.getContacts = () => {
+        const contacts = window.Store.Contact.getModelsArray();
+        return contacts.map(contact => window.WWebJS.getContactModel(contact));
+    };
+
+    window.WWebJS.mediaInfoToFile = ({ data, mimetype, filename }) => {
+        const binaryData = window.atob(data);
+
+        const buffer = new ArrayBuffer(binaryData.length);
+        const view = new Uint8Array(buffer);
+        for (let i = 0; i &lt; binaryData.length; i++) {
+            view[i] = binaryData.charCodeAt(i);
+        }
+
+        const blob = new Blob([buffer], { type: mimetype });
+        return new File([blob], filename, {
+            type: mimetype,
+            lastModified: Date.now()
+        });
+    };
+
+    window.WWebJS.arrayBufferToBase64 = (arrayBuffer) => {
+        let binary = '';
+        const bytes = new Uint8Array(arrayBuffer);
+        const len = bytes.byteLength;
+        for (let i = 0; i &lt; len; i++) {
+            binary += String.fromCharCode(bytes[i]);
+        }
+        return window.btoa(binary);
+    };
+
+    window.WWebJS.arrayBufferToBase64Async = (arrayBuffer) =>
+        new Promise((resolve, reject) => {
+            const blob = new Blob([arrayBuffer], {
+                type: 'application/octet-stream',
+            });
+            const fileReader = new FileReader();
+            fileReader.onload = () => {
+                const [, data] = fileReader.result.split(',');
+                resolve(data);
+            };
+            fileReader.onerror = (e) => reject(e);
+            fileReader.readAsDataURL(blob);
+        });
+
+    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)));
+    };
+
+    window.WWebJS.generateHash = async (length) => {
+        var result = '';
+        var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+        var charactersLength = characters.length;
+        for (var i = 0; i &lt; length; i++) {
+            result += characters.charAt(Math.floor(Math.random() * charactersLength));
+        }
+        return result;
+    };
+
+    /**
+     * Referenced from and modified:
+     * @see https://github.com/wppconnect-team/wa-js/commit/290ebfefe6021b3d17f7fdfdda5545bb0473b26f
+     */
+    window.WWebJS.generateWaveform = async (audioFile) => {
+        try {
+            const audioData = await audioFile.arrayBuffer();
+            const audioContext = new AudioContext();
+            const audioBuffer = await audioContext.decodeAudioData(audioData);
+
+            const rawData = audioBuffer.getChannelData(0);
+            const samples = 64;
+            const blockSize = Math.floor(rawData.length / samples);
+            const filteredData = [];
+            for (let i = 0; i &lt; samples; i++) {
+                const blockStart = blockSize * i;
+                let sum = 0;
+                for (let j = 0; j &lt; blockSize; j++) {
+                    sum = sum + Math.abs(rawData[blockStart + j]);
+                }
+                filteredData.push(sum / blockSize);
+            }
+
+            const multiplier = Math.pow(Math.max(...filteredData), -1);
+            const normalizedData = filteredData.map((n) => n * multiplier);
+
+            const waveform = new Uint8Array(
+                normalizedData.map((n) => Math.floor(100 * n))
+            );
+
+            return waveform;
+        } catch (e) {
+            return undefined;
+        }
+    };
+
+    window.WWebJS.sendClearChat = async (chatId) => {
+        let chat = window.Store.Chat.get(chatId);
+        if (chat !== undefined) {
+            await window.Store.SendClear.sendClear(chat, false);
+            return true;
+        }
+        return false;
+    };
+
+    window.WWebJS.sendDeleteChat = async (chatId) => {
+        let chat = window.Store.Chat.get(chatId);
+        if (chat !== undefined) {
+            await window.Store.SendDelete.sendDelete(chat);
+            return true;
+        }
+        return false;
+    };
+
+    window.WWebJS.sendChatstate = async (state, chatId) => {
+        if (window.Store.MDBackend) {
+            chatId = window.Store.WidFactory.createWid(chatId);
+        }
+        switch (state) {
+        case 'typing':
+            await window.Store.ChatState.sendChatStateComposing(chatId);
+            break;
+        case 'recording':
+            await window.Store.ChatState.sendChatStateRecording(chatId);
+            break;
+        case 'stop':
+            await window.Store.ChatState.sendChatStatePaused(chatId);
+            break;
+        default:
+            throw 'Invalid chatstate';
+        }
+
+        return true;
+    };
+
+    window.WWebJS.getLabelModel = label => {
+        let res = label.serialize();
+        res.hexColor = label.hexColor;
+
+        return res;
+    };
+
+    window.WWebJS.getLabels = () => {
+        const labels = window.Store.Label.getModelsArray();
+        return labels.map(label => window.WWebJS.getLabelModel(label));
+    };
+
+    window.WWebJS.getLabel = (labelId) => {
+        const label = window.Store.Label.get(labelId);
+        return window.WWebJS.getLabelModel(label);
+    };
+
+    window.WWebJS.getChatLabels = async (chatId) => {
+        const chat = await window.WWebJS.getChat(chatId);
+        return (chat.labels || []).map(id => window.WWebJS.getLabel(id));
+    };
+
+    window.WWebJS.getOrderDetail = async (orderId, token, chatId) => {
+        const chatWid = window.Store.WidFactory.createWid(chatId);
+        return window.Store.QueryOrder.queryOrder(chatWid, orderId, 80, 80, token);
+    };
+
+    window.WWebJS.getProductMetadata = async (productId) => {
+        let sellerId = window.Store.Conn.wid;
+        let product = await window.Store.QueryProduct.queryProduct(sellerId, productId);
+        if (product &amp;&amp; product.data) {
+            return product.data;
+        }
+
+        return undefined;
+    };
+
+    window.WWebJS.rejectCall = async (peerJid, id) => {
+        peerJid = peerJid.split('@')[0] + '@s.whatsapp.net';
+        let userId = window.Store.User.getMaybeMeUser().user + '@s.whatsapp.net';
+        const stanza = window.Store.SocketWap.wap('call', {
+            id: window.Store.SocketWap.generateId(),
+            from: window.Store.SocketWap.USER_JID(userId),
+            to: window.Store.SocketWap.USER_JID(peerJid),
+        }, [
+            window.Store.SocketWap.wap('reject', {
+                'call-id': id,
+                'call-creator': window.Store.SocketWap.USER_JID(peerJid),
+                count: '0',
+            })
+        ]);
+        await window.Store.Socket.deprecatedCastStanza(stanza);
+    };
+
+    window.WWebJS.cropAndResizeImage = async (media, options = {}) => {
+        if (!media.mimetype.includes('image'))
+            throw new Error('Media is not an image');
+
+        if (options.mimetype &amp;&amp; !options.mimetype.includes('image'))
+            delete options.mimetype;
+
+        options = Object.assign({ size: 640, mimetype: media.mimetype, quality: .75, asDataUrl: false }, options);
+
+        const img = await new Promise ((resolve, reject) => {
+            const img = new Image();
+            img.onload = () => resolve(img);
+            img.onerror = reject;
+            img.src = `data:${media.mimetype};base64,${media.data}`;
+        });
+
+        const sl = Math.min(img.width, img.height);
+        const sx = Math.floor((img.width - sl) / 2);
+        const sy = Math.floor((img.height - sl) / 2);
+
+        const canvas = document.createElement('canvas');
+        canvas.width = options.size;
+        canvas.height = options.size;
+
+        const ctx = canvas.getContext('2d');
+        ctx.drawImage(img, sx, sy, sl, sl, 0, 0, options.size, options.size);
+
+        const dataUrl = canvas.toDataURL(options.mimetype, options.quality);
+
+        if (options.asDataUrl)
+            return dataUrl;
+
+        return Object.assign(media, {
+            mimetype: options.mimeType,
+            data: dataUrl.replace(`data:${options.mimeType};base64,`, '')
+        });
+    };
+
+    window.WWebJS.setPicture = async (chatid, media) => {
+        const thumbnail = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 96 });
+        const profilePic = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 640 });
+
+        const chatWid = window.Store.WidFactory.createWid(chatid);
+        try {
+            const collection = window.Store.ProfilePicThumb.get(chatid);
+            if (!collection.canSet()) return;
+
+            const res = await window.Store.GroupUtils.sendSetPicture(chatWid, thumbnail, profilePic);
+            return res ? res.status === 200 : false;
+        } catch (err) {
+            if(err.name === 'ServerStatusCodeError') return false;
+            throw err;
+        }
+    };
+
+    window.WWebJS.deletePicture = async (chatid) => {
+        const chatWid = window.Store.WidFactory.createWid(chatid);
+        try {
+            const collection = window.Store.ProfilePicThumb.get(chatid);
+            if (!collection.canDelete()) return;
+
+            const res = await window.Store.GroupUtils.requestDeletePicture(chatWid);
+            return res ? res.status === 200 : false;
+        } catch (err) {
+            if(err.name === 'ServerStatusCodeError') return false;
+            throw err;
+        }
+    };
+    
+    window.WWebJS.getProfilePicThumbToBase64 = async (chatWid) => {
+        const profilePicCollection = await window.Store.ProfilePicThumb.find(chatWid);
+
+        const _readImageAsBase64 = (imageBlob) => {
+            return new Promise((resolve) => {
+                const reader = new FileReader();
+                reader.onloadend = function () {
+                    const base64Image = reader.result;
+                    if (base64Image == null) {
+                        resolve(undefined);
+                    } else {
+                        const base64Data = base64Image.toString().split(',')[1];
+                        resolve(base64Data);
+                    }
+                };
+                reader.readAsDataURL(imageBlob);
+            });
+        };
+
+        if (profilePicCollection?.img) {
+            try {
+                const response = await fetch(profilePicCollection.img);
+                if (response.ok) {
+                    const imageBlob = await response.blob();
+                    if (imageBlob) {
+                        const base64Image = await _readImageAsBase64(imageBlob);
+                        return base64Image;
+                    }
+                }
+            } catch (error) { /* empty */ }
+        }
+        return undefined;
+    };
+
+    window.WWebJS.getAddParticipantsRpcResult = async (groupMetadata, groupWid, participantWid) => {
+        const participantLidArgs = groupMetadata?.isLidAddressingMode
+            ? {
+                phoneNumber: participantWid,
+                lid: window.Store.LidUtils.getCurrentLid(participantWid)
+            }
+            : { phoneNumber: participantWid };
+
+        const iqTo = window.Store.WidToJid.widToGroupJid(groupWid);
+
+        const participantArgs =
+            participantLidArgs.lid
+                ? [{
+                    participantJid: window.Store.WidToJid.widToUserJid(participantLidArgs.lid),
+                    phoneNumberMixinArgs: {
+                        anyPhoneNumber: window.Store.WidToJid.widToUserJid(participantLidArgs.phoneNumber)
+                    }
+                }]
+                : [{
+                    participantJid: window.Store.WidToJid.widToUserJid(participantLidArgs.phoneNumber)
+                }];
+
+        let rpcResult, resultArgs;
+        const isOldImpl = window.compareWwebVersions(window.Debug.VERSION, '&lt;=', '2.2335.9');
+        const data = {
+            name: undefined,
+            code: undefined,
+            inviteV4Code: undefined,
+            inviteV4CodeExp: undefined
+        };
+
+        try {
+            rpcResult = await window.Store.GroupParticipants.sendAddParticipantsRPC({ participantArgs, iqTo });
+            resultArgs = isOldImpl
+                ? rpcResult.value.addParticipant[0].addParticipantsParticipantMixins
+                : rpcResult.value.addParticipant[0]
+                    .addParticipantsParticipantAddedOrNonRegisteredWaUserParticipantErrorLidResponseMixinGroup
+                    .value
+                    .addParticipantsParticipantMixins;
+        } catch (err) {
+            data.code = 400;
+            return data;
+        }
+
+        if (rpcResult.name === 'AddParticipantsResponseSuccess') {
+            const code = resultArgs?.value.error ?? '200';
+            data.name = resultArgs?.name;
+            data.code = +code;
+            data.inviteV4Code = resultArgs?.value.addRequestCode;
+            data.inviteV4CodeExp = resultArgs?.value.addRequestExpiration?.toString();
+        }
+
+        else if (rpcResult.name === 'AddParticipantsResponseClientError') {
+            const { code: code } = rpcResult.value.errorAddParticipantsClientErrors.value;
+            data.code = +code;
+        }
+
+        else if (rpcResult.name === 'AddParticipantsResponseServerError') {
+            const { code: code } = rpcResult.value.errorServerErrors.value;
+            data.code = +code;
+        }
+
+        return data;
+    };
+
+    window.WWebJS.membershipRequestAction = async (groupId, action, requesterIds, sleep) => {
+        const groupWid = window.Store.WidFactory.createWid(groupId);
+        const group = await window.Store.Chat.find(groupWid);
+        const toApprove = action === 'Approve';
+        let membershipRequests;
+        let response;
+        let result = [];
+
+        await window.Store.GroupMetadata.queryAndUpdate(groupWid);
+
+        if (!requesterIds?.length) {
+            membershipRequests = group.groupMetadata.membershipApprovalRequests._models.map(({ id }) => id);
+        } else {
+            !Array.isArray(requesterIds) &amp;&amp; (requesterIds = [requesterIds]);
+            membershipRequests = requesterIds.map(r => window.Store.WidFactory.createWid(r));
+        }
+
+        if (!membershipRequests.length) return [];
+
+        const participantArgs = membershipRequests.map(m => ({
+            participantArgs: [
+                {
+                    participantJid: window.Store.WidToJid.widToUserJid(m)
+                }
+            ]
+        }));
+
+        const groupJid = window.Store.WidToJid.widToGroupJid(groupWid);
+        
+        const _getSleepTime = (sleep) => {
+            if (!Array.isArray(sleep) || (sleep.length === 2 &amp;&amp; sleep[0] === sleep[1])) {
+                return sleep;
+            }
+            if (sleep.length === 1) {
+                return sleep[0];
+            }
+            sleep[1] - sleep[0] &lt; 100 &amp;&amp; (sleep[0] = sleep[1]) &amp;&amp; (sleep[1] += 100);
+            return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0];
+        };
+
+        const membReqResCodes = {
+            default: `An unknown error occupied while ${toApprove ? 'approving' : 'rejecting'} the participant membership request`,
+            400: 'ParticipantNotFoundError',
+            401: 'ParticipantNotAuthorizedError',
+            403: 'ParticipantForbiddenError',
+            404: 'ParticipantRequestNotFoundError',
+            408: 'ParticipantTemporarilyBlockedError',
+            409: 'ParticipantConflictError',
+            412: 'ParticipantParentLinkedGroupsResourceConstraintError',
+            500: 'ParticipantResourceConstraintError'
+        };
+
+        try {
+            for (const participant of participantArgs) {
+                response = await window.Store.MembershipRequestUtils.sendMembershipRequestsActionRPC({
+                    iqTo: groupJid,
+                    [toApprove ? 'approveArgs' : 'rejectArgs']: participant
+                });
+
+                if (response.name === 'MembershipRequestsActionResponseSuccess') {
+                    const value = toApprove
+                        ? response.value.membershipRequestsActionApprove
+                        : response.value.membershipRequestsActionReject;
+                    if (value?.participant) {
+                        const [_] = value.participant.map(p => {
+                            const error = toApprove
+                                ? value.participant[0].membershipRequestsActionAcceptParticipantMixins?.value.error
+                                : value.participant[0].membershipRequestsActionRejectParticipantMixins?.value.error;
+                            return {
+                                requesterId: window.Store.WidFactory.createWid(p.jid)._serialized,
+                                ...(error
+                                    ? { error: +error, message: membReqResCodes[error] || membReqResCodes.default }
+                                    : { message: `${toApprove ? 'Approved' : 'Rejected'} successfully` })
+                            };
+                        });
+                        _ &amp;&amp; result.push(_);
+                    }
+                } else {
+                    result.push({
+                        requesterId: window.Store.JidToWid.userJidToUserWid(participant.participantArgs[0].participantJid)._serialized,
+                        message: 'ServerStatusCodeError'
+                    });
+                }
+
+                sleep &amp;&amp;
+                    participantArgs.length > 1 &amp;&amp;
+                    participantArgs.indexOf(participant) !== participantArgs.length - 1 &amp;&amp;
+                    (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep))));
+            }
+            return result;
+        } catch (err) {
+            return [];
+        }
+    };
+};
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/util_InterfaceController.js.html b/docs/util_InterfaceController.js.html index 0cdbf70afe..9c0196c82a 100644 --- a/docs/util_InterfaceController.js.html +++ b/docs/util_InterfaceController.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: util/InterfaceController.js + whatsapp-web.js 1.23.0 » Source: util/InterfaceController.js @@ -15,7 +15,7 @@ @@ -81,7 +81,9 @@

Source: util/InterfaceController.js

async openChatWindowAt(msgId) { await this.pupPage.evaluate(async msgId => { let msg = await window.Store.Msg.get(msgId); - await window.Store.Cmd.openChatAt(msg.chat, msg.chat.getSearchContext(msg)); + let chat = await window.Store.Chat.find(msg.id.remote); + let searchContext = await window.Store.SearchContext(chat,msg); + await window.Store.Cmd.openChatAt(chat, searchContext); }, msgId); } @@ -101,7 +103,7 @@

Source: util/InterfaceController.js

*/ async closeRightDrawer() { await this.pupPage.evaluate(async () => { - await window.Store.Cmd.closeDrawerRight(); + await window.Store.DrawerManager.closeDrawerRight(); }); } @@ -110,6 +112,7 @@

Source: util/InterfaceController.js

*/ async getFeatures() { return await this.pupPage.evaluate(() => { + if(!window.Store.Features) throw new Error('This version of Whatsapp Web does not support features'); return window.Store.Features.F; }); } @@ -120,6 +123,7 @@

Source: util/InterfaceController.js

*/ async checkFeatureStatus(feature) { return await this.pupPage.evaluate((feature) => { + if(!window.Store.Features) throw new Error('This version of Whatsapp Web does not support features'); return window.Store.Features.supportsFeature(feature); }, feature); } @@ -130,6 +134,7 @@

Source: util/InterfaceController.js

*/ async enableFeatures(features) { await this.pupPage.evaluate((features) => { + if(!window.Store.Features) throw new Error('This version of Whatsapp Web does not support features'); for (const feature in features) { window.Store.Features.setFeature(features[feature], true); } @@ -142,6 +147,7 @@

Source: util/InterfaceController.js

*/ async disableFeatures(features) { await this.pupPage.evaluate((features) => { + if(!window.Store.Features) throw new Error('This version of Whatsapp Web does not support features'); for (const feature in features) { window.Store.Features.setFeature(features[feature], false); } @@ -160,7 +166,7 @@

Source: util/InterfaceController.js

diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index 50b56d03aa..d8ce40d09d 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -2,9 +2,9 @@ - + - whatsapp-web.js 1.17.1 » Source: util/Util.js + whatsapp-web.js 1.23.0 » Source: util/Util.js @@ -15,7 +15,7 @@ @@ -225,7 +225,7 @@

Source: util/Util.js

diff --git a/docs/webCache_LocalWebCache.js.html b/docs/webCache_LocalWebCache.js.html new file mode 100644 index 0000000000..ccdb09d839 --- /dev/null +++ b/docs/webCache_LocalWebCache.js.html @@ -0,0 +1,96 @@ + + + + + + + whatsapp-web.js 1.23.0 » Source: webCache/LocalWebCache.js + + + + + + + + +
+
+
+ +
+ +
+
const path = require('path');
+const fs = require('fs');
+
+const { WebCache, VersionResolveError } = require('./WebCache');
+
+/**
+ * LocalWebCache - Fetches a WhatsApp Web version from a local file store
+ * @param {object} options - options
+ * @param {string} options.path - Path to the directory where cached versions are saved, default is: "./.wwebjs_cache/" 
+ * @param {boolean} options.strict - If true, will throw an error if the requested version can't be fetched. If false, will resolve to the latest version.
+ */
+class LocalWebCache extends WebCache {
+    constructor(options = {}) {
+        super();
+
+        this.path = options.path || './.wwebjs_cache/';
+        this.strict = options.strict || false;
+    }
+
+    async resolve(version) {
+        const filePath = path.join(this.path, `${version}.html`);
+        
+        try {
+            return fs.readFileSync(filePath, 'utf-8');
+        }
+        catch (err) {
+            if (this.strict) throw new VersionResolveError(`Couldn't load version ${version} from the cache`);
+            return null;
+        }
+    }
+
+    async persist(indexHtml) {
+        // extract version from index (e.g. manifest-2.2206.9.json -> 2.2206.9)
+        const version = indexHtml.match(/manifest-([\d\\.]+)\.json/)[1];
+        if(!version) return;
+   
+        const filePath = path.join(this.path, `${version}.html`);
+        fs.mkdirSync(this.path, { recursive: true });
+        fs.writeFileSync(filePath, indexHtml);
+    }
+}
+
+module.exports = LocalWebCache;
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/webCache_RemoteWebCache.js.html b/docs/webCache_RemoteWebCache.js.html new file mode 100644 index 0000000000..75a643ed65 --- /dev/null +++ b/docs/webCache_RemoteWebCache.js.html @@ -0,0 +1,93 @@ + + + + + + + whatsapp-web.js 1.23.0 » Source: webCache/RemoteWebCache.js + + + + + + + + +
+
+
+ +
+ +
+
const fetch = require('node-fetch');
+const { WebCache, VersionResolveError } = require('./WebCache');
+
+/**
+ * RemoteWebCache - Fetches a WhatsApp Web version index from a remote server
+ * @param {object} options - options
+ * @param {string} options.remotePath - Endpoint that should be used to fetch the version index. Use {version} as a placeholder for the version number.
+ * @param {boolean} options.strict - If true, will throw an error if the requested version can't be fetched. If false, will resolve to the latest version. Defaults to false.
+ */
+class RemoteWebCache extends WebCache {
+    constructor(options = {}) {
+        super();
+
+        if (!options.remotePath) throw new Error('webVersionCache.remotePath is required when using the remote cache');
+        this.remotePath = options.remotePath;
+        this.strict = options.strict || false;
+    }
+
+    async resolve(version) {
+        const remotePath = this.remotePath.replace('{version}', version);
+
+        try {
+            const cachedRes = await fetch(remotePath);
+            if (cachedRes.ok) {
+                return cachedRes.text();
+            }
+        } catch (err) {
+            console.error(`Error fetching version ${version} from remote`, err);
+        }
+
+        if (this.strict) throw new VersionResolveError(`Couldn't load version ${version} from the archive`);
+        return null;         
+    }
+
+    async persist() {
+        // Nothing to do here
+    }
+}
+
+module.exports = RemoteWebCache;
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/webCache_WebCache.js.html b/docs/webCache_WebCache.js.html new file mode 100644 index 0000000000..a19a40e10c --- /dev/null +++ b/docs/webCache_WebCache.js.html @@ -0,0 +1,67 @@ + + + + + + + whatsapp-web.js 1.23.0 » Source: webCache/WebCache.js + + + + + + + + +
+
+
+ +
+ +
+
/**
+ * Default implementation of a web version cache that does nothing.
+ */
+class WebCache {
+    async resolve() { return null; }
+    async persist() { }
+}
+
+class VersionResolveError extends Error { }
+
+module.exports = {
+    WebCache,
+    VersionResolveError
+};
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/example.js b/example.js index 99dc7ea00c..fca5bcfa3d 100644 --- a/example.js +++ b/example.js @@ -1,8 +1,12 @@ -const { Client, Location, List, Buttons, LocalAuth} = require('./index'); +const { Client, Location, Poll, List, Buttons, LocalAuth } = require('./index'); const client = new Client({ authStrategy: new LocalAuth(), - puppeteer: { headless: false } + // proxyAuthentication: { username: 'username', password: 'password' }, + puppeteer: { + // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], + headless: true + } }); client.initialize(); @@ -62,6 +66,9 @@ client.on('message', async msg => { } else if (msg.body.startsWith('!echo ')) { // Replies with the same message msg.reply(msg.body.slice(6)); + } else if (msg.body.startsWith('!preview ')) { + const text = msg.body.slice(9); + msg.reply(text, null, { linkPreview: true }); } else if (msg.body.startsWith('!desc ')) { // Change the group description let chat = await msg.getChat(); @@ -87,6 +94,78 @@ client.on('message', async msg => { } catch (e) { msg.reply('That invite code seems to be invalid.'); } + } else if (msg.body.startsWith('!addmembers')) { + const group = await msg.getChat(); + const result = await group.addParticipants(['number1@c.us', 'number2@c.us', 'number3@c.us']); + /** + * The example of the {@link result} output: + * + * { + * 'number1@c.us': { + * code: 200, + * message: 'The participant was added successfully', + * isInviteV4Sent: false + * }, + * 'number2@c.us': { + * code: 403, + * message: 'The participant can be added by sending private invitation only', + * isInviteV4Sent: true + * }, + * 'number3@c.us': { + * code: 404, + * message: 'The phone number is not registered on WhatsApp', + * isInviteV4Sent: false + * } + * } + * + * For more usage examples: + * @see https://github.com/pedroslopez/whatsapp-web.js/pull/2344#usage-example1 + */ + console.log(result); + } else if (msg.body === '!creategroup') { + const partitipantsToAdd = ['number1@c.us', 'number2@c.us', 'number3@c.us']; + const result = await client.createGroup('Group Title', partitipantsToAdd); + /** + * The example of the {@link result} output: + * { + * title: 'Group Title', + * gid: { + * server: 'g.us', + * user: '1111111111', + * _serialized: '1111111111@g.us' + * }, + * participants: { + * 'botNumber@c.us': { + * statusCode: 200, + * message: 'The participant was added successfully', + * isGroupCreator: true, + * isInviteV4Sent: false + * }, + * 'number1@c.us': { + * statusCode: 200, + * message: 'The participant was added successfully', + * isGroupCreator: false, + * isInviteV4Sent: false + * }, + * 'number2@c.us': { + * statusCode: 403, + * message: 'The participant can be added by sending private invitation only', + * isGroupCreator: false, + * isInviteV4Sent: true + * }, + * 'number3@c.us': { + * statusCode: 404, + * message: 'The phone number is not registered on WhatsApp', + * isGroupCreator: false, + * isInviteV4Sent: false + * } + * } + * } + * + * For more usage examples: + * @see https://github.com/pedroslopez/whatsapp-web.js/pull/2344#usage-example2 + */ + console.log(result); } else if (msg.body === '!groupinfo') { let chat = await msg.getChat(); if (chat.isGroup) { @@ -136,20 +215,77 @@ client.on('message', async msg => { const attachmentData = await quotedMsg.downloadMedia(); client.sendMessage(msg.from, attachmentData, { caption: 'Here\'s your requested media.' }); } + if (quotedMsg.hasMedia && quotedMsg.type === 'audio') { + const audio = await quotedMsg.downloadMedia(); + await client.sendMessage(msg.from, audio, { sendAudioAsVoice: true }); + } + } else if (msg.body === '!isviewonce' && msg.hasQuotedMsg) { + const quotedMsg = await msg.getQuotedMessage(); + if (quotedMsg.hasMedia) { + const media = await quotedMsg.downloadMedia(); + await client.sendMessage(msg.from, media, { isViewOnce: true }); + } } else if (msg.body === '!location') { - msg.reply(new Location(37.422, -122.084, 'Googleplex\nGoogle Headquarters')); + // only latitude and longitude + await msg.reply(new Location(37.422, -122.084)); + // location with name only + await msg.reply(new Location(37.422, -122.084, { name: 'Googleplex' })); + // location with address only + await msg.reply(new Location(37.422, -122.084, { address: '1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA' })); + // location with name, address and url + await msg.reply(new Location(37.422, -122.084, { name: 'Googleplex', address: '1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA', url: 'https://google.com' })); } else if (msg.location) { msg.reply(msg.location); } else if (msg.body.startsWith('!status ')) { const newStatus = msg.body.split(' ')[1]; await client.setStatus(newStatus); msg.reply(`Status was updated to *${newStatus}*`); - } else if (msg.body === '!mention') { - const contact = await msg.getContact(); + } else if (msg.body === '!mentionUsers') { const chat = await msg.getChat(); - chat.sendMessage(`Hi @${contact.number}!`, { - mentions: [contact] + const userNumber = 'XXXXXXXXXX'; + /** + * To mention one user you can pass user's ID to 'mentions' property as is, + * without wrapping it in Array, and a user's phone number to the message body: + */ + await chat.sendMessage(`Hi @${userNumber}`, { + mentions: userNumber + '@c.us' + }); + // To mention a list of users: + await chat.sendMessage(`Hi @${userNumber}, @${userNumber}`, { + mentions: [userNumber + '@c.us', userNumber + '@c.us'] + }); + } else if (msg.body === '!mentionGroups') { + const chat = await msg.getChat(); + const groupId = 'YYYYYYYYYY@g.us'; + /** + * Sends clickable group mentions, the same as user mentions. + * When the mentions are clicked, it opens a chat with the mentioned group. + * The 'groupMentions.subject' can be custom + * + * @note The user that does not participate in the mentioned group, + * will not be able to click on that mentioned group, the same if the group does not exist + * + * To mention one group: + */ + await chat.sendMessage(`Check the last message here: @${groupId}`, { + groupMentions: { subject: 'GroupSubject', id: groupId } + }); + // To mention a list of groups: + await chat.sendMessage(`Check the last message in these groups: @${groupId}, @${groupId}`, { + groupMentions: [ + { subject: 'FirstGroup', id: groupId }, + { subject: 'SecondGroup', id: groupId } + ] + }); + } else if (msg.body === '!getGroupMentions') { + // To get group mentions from a message: + const groupId = 'ZZZZZZZZZZ@g.us'; + const msg = await client.sendMessage('chatId', `Check the last message here: @${groupId}`, { + groupMentions: { subject: 'GroupSubject', id: groupId } }); + /** {@link groupMentions} is an array of `GroupChat` */ + const groupMentions = await msg.getGroupMentions(); + console.log(groupMentions); } else if (msg.body === '!delete') { if (msg.hasQuotedMsg) { const quotedMsg = await msg.getQuotedMessage(); @@ -189,22 +325,136 @@ client.on('message', async msg => { client.interface.openChatWindowAt(quotedMsg.id._serialized); } } else if (msg.body === '!buttons') { - let button = new Buttons('Button body',[{body:'bt1'},{body:'bt2'},{body:'bt3'}],'title','footer'); + let button = new Buttons('Button body', [{ body: 'bt1' }, { body: 'bt2' }, { body: 'bt3' }], 'title', 'footer'); client.sendMessage(msg.from, button); } else if (msg.body === '!list') { - let sections = [{title:'sectionTitle',rows:[{title:'ListItem1', description: 'desc'},{title:'ListItem2'}]}]; - let list = new List('List body','btnText',sections,'Title','footer'); + let sections = [ + { title: 'sectionTitle', rows: [{ title: 'ListItem1', description: 'desc' }, { title: 'ListItem2' }] } + ]; + let list = new List('List body', 'btnText', sections, 'Title', 'footer'); client.sendMessage(msg.from, list); } else if (msg.body === '!reaction') { msg.react('👍'); + } else if (msg.body === '!sendpoll') { + /** By default the poll is created as a single choice poll: */ + await msg.reply(new Poll('Winter or Summer?', ['Winter', 'Summer'])); + /** If you want to provide a multiple choice poll, add allowMultipleAnswers as true: */ + await msg.reply(new Poll('Cats or Dogs?', ['Cats', 'Dogs'], { allowMultipleAnswers: true })); + /** + * You can provide a custom message secret, it can be used as a poll ID: + * @note It has to be a unique vector with a length of 32 + */ + await msg.reply( + new Poll('Cats or Dogs?', ['Cats', 'Dogs'], { + messageSecret: [ + 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }) + ); + } else if (msg.body === '!edit') { + if (msg.hasQuotedMsg) { + const quotedMsg = await msg.getQuotedMessage(); + if (quotedMsg.fromMe) { + quotedMsg.edit(msg.body.replace('!edit', '')); + } else { + msg.reply('I can only edit my own messages'); + } + } + } else if (msg.body === '!updatelabels') { + const chat = await msg.getChat(); + await chat.changeLabels([0, 1]); + } else if (msg.body === '!addlabels') { + const chat = await msg.getChat(); + let labels = (await chat.getLabels()).map((l) => l.id); + labels.push('0'); + labels.push('1'); + await chat.changeLabels(labels); + } else if (msg.body === '!removelabels') { + const chat = await msg.getChat(); + await chat.changeLabels([]); + } else if (msg.body === '!approverequest') { + /** + * Presented an example for membership request approvals, the same examples are for the request rejections. + * To approve the membership request from a specific user: + */ + await client.approveGroupMembershipRequests(msg.from, { requesterIds: 'number@c.us' }); + /** The same for execution on group object (no need to provide the group ID): */ + const group = await msg.getChat(); + await group.approveGroupMembershipRequests({ requesterIds: 'number@c.us' }); + /** To approve several membership requests: */ + const approval = await client.approveGroupMembershipRequests(msg.from, { + requesterIds: ['number1@c.us', 'number2@c.us'] + }); + /** + * The example of the {@link approval} output: + * [ + * { + * requesterId: 'number1@c.us', + * message: 'Rejected successfully' + * }, + * { + * requesterId: 'number2@c.us', + * error: 404, + * message: 'ParticipantRequestNotFoundError' + * } + * ] + * + */ + console.log(approval); + /** To approve all the existing membership requests (simply don't provide any user IDs): */ + await client.approveGroupMembershipRequests(msg.from); + /** To change the sleep value to 300 ms: */ + await client.approveGroupMembershipRequests(msg.from, { + requesterIds: ['number1@c.us', 'number2@c.us'], + sleep: 300 + }); + /** To change the sleep value to random value between 100 and 300 ms: */ + await client.approveGroupMembershipRequests(msg.from, { + requesterIds: ['number1@c.us', 'number2@c.us'], + sleep: [100, 300] + }); + /** To explicitly disable the sleep: */ + await client.approveGroupMembershipRequests(msg.from, { + requesterIds: ['number1@c.us', 'number2@c.us'], + sleep: null + }); + } else if (msg.body === '!pinmsg') { + /** + * Pins a message in a chat, a method takes a number in seconds for the message to be pinned. + * WhatsApp default values for duration to pass to the method are: + * 1. 86400 for 24 hours + * 2. 604800 for 7 days + * 3. 2592000 for 30 days + * You can pass your own value: + */ + const result = await msg.pin(60); // Will pin a message for 1 minute + console.log(result); // True if the operation completed successfully, false otherwise } }); -client.on('message_create', (msg) => { +client.on('message_create', async (msg) => { // Fired on all message creations, including your own if (msg.fromMe) { // do stuff here } + + // Unpins a message + if (msg.fromMe && msg.body.startsWith('!unpin')) { + const pinnedMsg = await msg.getQuotedMessage(); + if (pinnedMsg) { + // Will unpin a message + const result = await pinnedMsg.unpin(); + console.log(result); // True if the operation completed successfully, false otherwise + } + } +}); + +client.on('message_ciphertext', (msg) => { + // Receiving new incoming messages that have been encrypted + // msg.type === 'ciphertext' + msg.body = 'Waiting for this message. Check your phone.'; + + // do stuff here }); client.on('message_revoke_everyone', async (after, before) => { @@ -231,7 +481,7 @@ client.on('message_ack', (msg, ack) => { ACK_PLAYED: 4 */ - if(ack == 3) { + if (ack == 3) { // The message was read } }); @@ -254,10 +504,93 @@ client.on('group_update', (notification) => { }); client.on('change_state', state => { - console.log('CHANGE STATE', state ); + console.log('CHANGE STATE', state); +}); + +// Change to false if you don't want to reject incoming calls +let rejectCalls = true; + +client.on('call', async (call) => { + console.log('Call received, rejecting. GOTO Line 261 to disable', call); + if (rejectCalls) await call.reject(); + await client.sendMessage(call.from, `[${call.fromMe ? 'Outgoing' : 'Incoming'}] Phone call from ${call.from}, type ${call.isGroup ? 'group' : ''} ${call.isVideo ? 'video' : 'audio'} call. ${rejectCalls ? 'This call was automatically rejected by the script.' : ''}`); }); client.on('disconnected', (reason) => { console.log('Client was logged out', reason); }); +client.on('contact_changed', async (message, oldId, newId, isContact) => { + /** The time the event occurred. */ + const eventTime = (new Date(message.timestamp * 1000)).toLocaleString(); + + console.log( + `The contact ${oldId.slice(0, -5)}` + + `${!isContact ? ' that participates in group ' + + `${(await client.getChatById(message.to ?? message.from)).name} ` : ' '}` + + `changed their phone number\nat ${eventTime}.\n` + + `Their new phone number is ${newId.slice(0, -5)}.\n`); + + /** + * Information about the @param {message}: + * + * 1. If a notification was emitted due to a group participant changing their phone number: + * @param {message.author} is a participant's id before the change. + * @param {message.recipients[0]} is a participant's id after the change (a new one). + * + * 1.1 If the contact who changed their number WAS in the current user's contact list at the time of the change: + * @param {message.to} is a group chat id the event was emitted in. + * @param {message.from} is a current user's id that got an notification message in the group. + * Also the @param {message.fromMe} is TRUE. + * + * 1.2 Otherwise: + * @param {message.from} is a group chat id the event was emitted in. + * @param {message.to} is @type {undefined}. + * Also @param {message.fromMe} is FALSE. + * + * 2. If a notification was emitted due to a contact changing their phone number: + * @param {message.templateParams} is an array of two user's ids: + * the old (before the change) and a new one, stored in alphabetical order. + * @param {message.from} is a current user's id that has a chat with a user, + * whos phone number was changed. + * @param {message.to} is a user's id (after the change), the current user has a chat with. + */ +}); + +client.on('group_admin_changed', (notification) => { + if (notification.type === 'promote') { + /** + * Emitted when a current user is promoted to an admin. + * {@link notification.author} is a user who performs the action of promoting/demoting the current user. + */ + console.log(`You were promoted by ${notification.author}`); + } else if (notification.type === 'demote') + /** Emitted when a current user is demoted to a regular user. */ + console.log(`You were demoted by ${notification.author}`); +}); + +client.on('group_membership_request', async (notification) => { + /** + * The example of the {@link notification} output: + * { + * id: { + * fromMe: false, + * remote: 'groupId@g.us', + * id: '123123123132132132', + * participant: 'number@c.us', + * _serialized: 'false_groupId@g.us_123123123132132132_number@c.us' + * }, + * body: '', + * type: 'created_membership_requests', + * timestamp: 1694456538, + * chatId: 'groupId@g.us', + * author: 'number@c.us', + * recipientIds: [] + * } + * + */ + console.log(notification); + /** You can approve or reject the newly appeared membership request: */ + await client.approveGroupMembershipRequestss(notification.chatId, notification.author); + await client.rejectGroupMembershipRequests(notification.chatId, notification.author); +}); \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 76ac264455..329a419bf4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -35,12 +35,8 @@ declare namespace WAWebJS { /** Unpins the Chat and returns its new Pin state */ unpinChat(chatId: string): Promise - /** - * Create a new group - * @param name group title - * @param participants an array of Contacts or contact IDs to add to the group - */ - createGroup(name: string, participants: Contact[] | string[]): Promise + /** Creates a new group */ + createGroup(title: string, participants?: string | Contact | Contact[] | string[], options?: CreateGroupOptions): Promise /** Closes the client */ destroy(): Promise @@ -60,6 +56,9 @@ declare namespace WAWebJS { /** Get contact instance by ID */ getContactById(contactId: string): Promise + /** Get message by ID */ + getMessageById(messageId: string): Promise + /** Get all current contact instances */ getContacts(): Promise @@ -71,6 +70,9 @@ declare namespace WAWebJS { /** Get all current Labels */ getLabels(): Promise + + /** Change labels in chats */ + addOrRemoveLabels(labelIds: Array, chatIds: Array): Promise /** Get Label instance by ID */ getLabelById(labelId: string): Promise