diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4fa8524374..6757fff859 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,72 +1,71 @@ -name: '🐛 Bug report' +name: '🐛 Bug Report' description: Create a report to help us improve labels: bug body: - type: markdown attributes: value: | - Thank you for reporting an issue :pray:. + ### Thank you for reporting an issue. - This issue tracker is for reporting bugs found in [`whatsapp-web.js`](https://github.com/pedroslopez/whatsapp-web.js). + ### Here are some important points to consider before submitting a new bug or issue: - If you have a question about how to achieve something and are struggling, please post a question in our [Discord server](https://discord.gg/wyKybbF) instead. + - Please report only issues related to the `whatsapp-web.js` library. + - Duplicate issues will be closed. + - Any issue opened as a question will be closed. - Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: - - `whatsapp-web.js` [Issues tab](https://github.com/pedroslopez/whatsapp-web.js/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) - - `whatsapp-web.js` [closed Issues tab](https://github.com/pedroslopez/whatsapp-web.js/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed) + ### If you have any question about how to achieve something and are struggling, please post a question in our [Discord Server](https://discord.gg/wyKybbF) instead. + + ### Please check the links below to see if you can find a solution there: + + - [Open Issues](https://github.com/pedroslopez/whatsapp-web.js/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) + - [Closed Issues](https://github.com/pedroslopez/whatsapp-web.js/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed) + - [Library Documentation](https://docs.wwebjs.dev/) + - [Guide](https://wwebjs.dev/guide) + - [JavaScript Guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide) + - [Node.js Documentation](https://nodejs.org/en/docs/) - The more information you fill in, the better the community can help you. + ### The more information you fill in, the better the community can help you. - type: checkboxes attributes: label: Is there an existing issue for this? description: Please search to see if an issue already exists for the bug you encountered. options: - - label: I have searched the existing issues + - label: I have searched the existing issues. + required: true + - type: checkboxes + attributes: + label: Is this a problem caused by your code, or is it specifically because of the library? + description: Please check your code once again to make sure it is correct. + options: + - label: I have double-checked my code carefully. required: true - type: textarea id: description attributes: - label: Describe the bug + label: Describe the bug. description: Provide a clear and concise description of the challenge you are running into. validations: required: true - type: textarea id: expected attributes: - label: Expected behavior + label: Expected Behavior description: Provide a clear and concise description of what you expected to happen. placeholder: | - As a user, I expected ___ behavior but I am seeing ___ + As a user, I expected ___ behavior, but I am seeing ___ validations: required: true - type: textarea id: steps attributes: label: Steps to Reproduce the Bug or Issue - description: Describe the steps we have to take to reproduce the behavior. + description: Describe the steps we have to take to reproduce the behavior. Additionally, **please include the code snippet you used.** Use [syntax highlighting](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) for more readability. placeholder: | - 1. Do X - 2. Do Y - 3. Do Z - 4. See error - validations: - required: true - - type: textarea - id: relevant_code - attributes: - label: Relevant Code - description: If applicable, add code snippets to help explain your problem. - validations: - required: false - - type: dropdown - id: browser_type - attributes: - label: Browser Type - description: What web browser are you using? - options: - - Chromium - - Google Chrome - - Other (please write in Additional Context) + Your steps to reproduce the bug here... + + ```js + // your code here + ``` validations: required: true - type: dropdown @@ -78,28 +77,64 @@ body: - WhatsApp Business validations: required: true - - type: dropdown - id: multidevice + - type: input + id: browser_type attributes: - label: Does your WhatsApp account have multidevice enabled? - options: - - Yes, I am using Multi Device - - No, I am not using Multi Device + label: Browser Type + description: What web browser are you using? **Also provide the browser version.** + placeholder: Chromium | Google Chrome | other (provide the type) validations: required: true - - type: textarea + - type: input + id: os + attributes: + label: Operation System Type + description: What OS are you using? **Also provide its version.** + placeholder: Mac | Windows | Linux | Docker + Ubuntu | other (provide the type) + validations: + required: true + - type: input + id: phone_os + attributes: + label: Phone OS Type + description: What OS are you using on your phone? **Also provide its version.** + placeholder: Android | iOS | other (provide the type) + validations: + required: true + - type: input + id: lib_version attributes: - label: Environment - description: | - - OS: [e.g. Mac, Windows, Linux, Docker + Ubuntu 18, etc] - - Phone OS: [e.g. Android, iOS] - - whatsapp-web.js version [e.g. 1.2.3] - - WhatsApp Web version [run `await client.getWWebVersion()`]: - - Node.js Version [e.g. 1.2.3] + label: WhatsApp-Web.js Version + description: What library version are you using? Check it in `package.json` file. + validations: + required: true + - type: input + id: wweb_version + attributes: + label: WhatsApp Web Version + description: What WhatsApp Web version are you using? Run `await client.getWWebVersion()`. + validations: + required: true + - type: input + id: node_version + attributes: + label: Node.js Version + description: What Node.js version are you using? Run `node -v` in your terminal. + validations: + required: true + - type: dropdown + id: auth_type + attributes: + label: Authentication Strategy + description: What authenctication strategy are you using? + options: + - LocalAuth + - RemoteAuth + - NoAuth validations: required: true - type: textarea id: additional attributes: - label: Additional context - description: Add any other context about the problem here. + label: Additional Context + description: Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9f4bc70ad8..67b3159db1 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,21 +1,50 @@ -name: 🚀 Feature request +name: 🚀 Feature Request description: Suggest an idea for this project labels: enhancement - body: + - type: markdown + attributes: + value: | + ### Thank you for suggesting a new idea for this project. + + ### Here are some important points to consider before continuing: + + - Please submit only feature requests that related to the `whatsapp-web.js` library. + - Duplicate feature requests will be closed. + - Any feature request opened as a question will be closed. + + ### If you have any question about how to achieve something and are struggling, please post a question in our [Discord Server](https://discord.gg/wyKybbF) instead. + + ### Please check the links below to see if you can find a solution there: + + - [Open Pull Requests](https://github.com/pedroslopez/whatsapp-web.js/pulls?q=is%3Apr+is%3Aopen+-author%3Aapp%2Fdependabot) + - [Merged Pull Requests](https://github.com/pedroslopez/whatsapp-web.js/pulls?q=is%3Apr+is%3Amerged+-author%3Aapp%2Fdependabot) + - [Library Documentation](https://docs.wwebjs.dev/) + - [Guide](https://wwebjs.dev/guide) + - [JavaScript Guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide) + - [Node.js Documentation](https://nodejs.org/en/docs/) + - type: checkboxes attributes: - label: Is there an existing issue for this? - description: Please search to see if an issue related to this feature request already exists. + label: Is the feature you are looking for already implemented in the library? + description: Please check to see if an issue related to this feature request has [already been implemented](https://github.com/pedroslopez/whatsapp-web.js/pulls?q=is%3Apr+is%3Amerged+-author%3Aapp%2Fdependabot). options: - - label: I have searched the existing issues + - label: I have reviewed the implemented features. + required: true + + - type: checkboxes + attributes: + label: Is your "solution" already implemented in an opened pull request? + description: Please check the [open pull requests](https://github.com/pedroslopez/whatsapp-web.js/pulls?q=is%3Apr+is%3Aopen+-author%3Aapp%2Fdependabot) to see if there is already an issue related to this feature request. + options: + - label: I have reviewed the existing pull requests. required: true - type: textarea attributes: label: Is your feature request related to a problem? Please describe. - description: A concise description of the problem you are facing or the motivetion behind this feature request. - placeholder: I faced a problem due to ... + description: A concise description of the problem you are facing or the motivation behind this feature request. + placeholder: I faced a problem due to... validations: required: false @@ -28,15 +57,15 @@ body: - type: textarea attributes: - label: Describe an alternate solution. + label: Describe an alternate solution if you have one. description: Is there any other approach to solve the problem? validations: required: false - type: textarea attributes: - label: Additional context + label: Additional Context description: | Links? Screenshots? References? Anything that will give us more context about what you would like to see! validations: - required: false + required: false \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ad41c055da..8d6f50029c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,29 +1,43 @@ # PR Details - + ## Description - + -## Related Issue +## Related Issue(s) - - + + + + + + ## Motivation and Context - - + + ## How Has This Been Tested - - + + +### Environment + + +- Machine OS: +- Phone OS: +- Library Version: +- WhatsApp Web Version: +- Puppeteer Version: +- Browser Type and Version: +- Node Version: ## Types of changes - + - [ ] Dependency change - [ ] Bug fix (non-breaking change which fixes an issue) @@ -32,10 +46,8 @@ ## Checklist - + - [ ] My code follows the code style of this project. - [ ] I have updated the documentation accordingly (index.d.ts). - - - +- [ ] I have updated the usage example accordingly (example.js) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 12b53d4ca5..590ddff4f9 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,7 @@ typings/ # local version cache .wwebjs_cache/ + +# IDE's +.idea +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 638f8d2a99..f5a0ebebd9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

npm Depfu - WhatsApp_Web 2.2346.52 + WhatsApp_Web 2.2346.52 Discord server


diff --git a/docs/Base.html b/docs/Base.html index c6a0cbb43e..fdeaf0d866 100644 --- a/docs/Base.html +++ b/docs/Base.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Base + whatsapp-web.js 1.26.0 » Class: Base @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@

new Base diff --git a/docs/BaseAuthStrategy.html b/docs/BaseAuthStrategy.html index 80230baf6b..535a68cf55 100644 --- a/docs/BaseAuthStrategy.html +++ b/docs/BaseAuthStrategy.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: BaseAuthStrategy + whatsapp-web.js 1.26.0 » Class: BaseAuthStrategy @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@

new BaseAuthStrategy diff --git a/docs/BusinessContact.html b/docs/BusinessContact.html index 4c243a3319..5229c4e221 100644 --- a/docs/BusinessContact.html +++ b/docs/BusinessContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: BusinessContact + whatsapp-web.js 1.26.0 » Class: BusinessContact @@ -15,7 +15,7 @@ @@ -326,7 +326,7 @@

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

Parameter

diff --git a/docs/Call.html b/docs/Call.html index b7489d0911..fb5c975511 100644 --- a/docs/Call.html +++ b/docs/Call.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Call + whatsapp-web.js 1.26.0 » Class: Call @@ -15,7 +15,7 @@ @@ -168,7 +168,7 @@

reject<
diff --git a/docs/Chat.html b/docs/Chat.html index 706ea57e82..cdaa946380 100644 --- a/docs/Chat.html +++ b/docs/Chat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Chat + whatsapp-web.js 1.26.0 » Class: Chat @@ -15,7 +15,7 @@ @@ -545,7 +545,7 @@

unpin diff --git a/docs/Client.html b/docs/Client.html index 46398b9e61..6a46128b22 100644 --- a/docs/Client.html +++ b/docs/Client.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Client + whatsapp-web.js 1.26.0 » Class: Client @@ -15,7 +15,7 @@ @@ -26,7 +26,7 @@
-

Property

+

Properties

@@ -45,8 +45,18 @@

Property

+
+
pupBrowser
+
+
+
+
+
pupPage
+
+
+
@@ -70,6 +80,9 @@

Methods

archiveChat()
+
attachEventListeners()
+
+
createGroup(title, participants, options)
@@ -100,13 +113,16 @@

Methods

getContactById(contactId)
+
getContactDeviceCount(contactId)
+
+
+
getContacts()
+
+
-
getContacts()
-
-
getCountryCode(number)
@@ -140,6 +156,9 @@

Methods

initialize()
+
inject()
+
+
isRegisteredUser(id)
@@ -149,10 +168,6 @@

Methods

markChatUnread(chatId)
-
-
- +
-
group_admin_changed
-
-
group_join
@@ -259,13 +293,16 @@

Events

message
+
message_ack
+
+
+
message_ciphertext
+
+
-
message_ack
-
-
message_create
@@ -287,6 +324,9 @@

Events

ready
+
vote_update
+
+
@@ -494,7 +534,7 @@

Parameters

 

-

Ffmpeg path to use when formating videos to webp while sending stickers

+

Ffmpeg path to use when formatting videos to webp while sending stickers

@@ -545,6 +585,8 @@

Parameters

Client#event:message_create
Client#event:message_revoke_me
Client#event:message_revoke_everyone
+
Client#event:message_ciphertext
+
Client#event:message_edit
Client#event:media_uploaded
Client#event:group_join
Client#event:group_leave
@@ -554,16 +596,25 @@

Parameters

Client#event:contact_changed
Client#event:group_admin_changed
Client#event:group_membership_request
+
Client#event:vote_update
-

Property

+

Properties

info  ClientInfo

Current connection information

+

pupBrowser +  puppeteer.Browser

+
+
+

pupPage +  puppeteer.Page

+
+

Methods

@@ -759,6 +810,41 @@

archiveChatasync +

attachEventListeners()

+

Attach event listeners to WA Web + Private function

+
+

Property

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

reinject

+
+

boolean

+
+

 

+
+

is this a reinject?

+
+
+
+
+
async

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

Creates a new group

@@ -1047,6 +1133,45 @@

Parameter

async
+

getContactDeviceCount(contactId) → number

+

Get user device count by ID + Each WaWeb Connection counts as one device, and the phone (if exists) counts as one + So for a non-enterprise user with one WaWeb connection it should return "2"

+
+

Parameter

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

contactId

+
+

string

+
+

 

+
+
+
+
+
Returns
+
+

number 

+
+
+
async

getContacts() → Promise containing Array of Contact

Get all current contact instances

@@ -1357,6 +1482,41 @@

initialize

async
+

inject()

+

Injection logic + Private function

+
+

Property

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

reinject

+
+

boolean

+
+

 

+
+

is this a reinject?

+
+
+
+
+
async

isRegisteredUser(id) → Promise containing Boolean

Check if a given ID is registered in whatsapp

@@ -1548,6 +1708,63 @@

Parameters

async
+

requestPairingCode(phoneNumber, showNotification) → Promise containing string

+

Request authentication via pairing code instead of QR code

+
+

Parameters

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

phoneNumber

+
+

string

+
+

 

+
+

Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)

+
+

showNotification

+
+

boolean

+
+

 

+
+

Show notification to pair on phone number

+

Defaults to true.

+
+
+
+
Returns
+
+

Promise containing string  +

    +

    +
  • Returns a pairing code in format "ABCDEFGH"
  • +
+
+
+
async

resetState()

Force reset of connection state for the client

@@ -1770,6 +1987,142 @@

Parameter

async
+

setAutoDownloadAudio(flag)

+

Setting autoload download audio

+
+

Parameter

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

flag

+
+

boolean

+
+

 

+
+

true/false

+
+
+
+
+
async
+

setAutoDownloadDocuments(flag)

+

Setting autoload download documents

+
+

Parameter

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

flag

+
+

boolean

+
+

 

+
+

true/false

+
+
+
+
+
async
+

setAutoDownloadPhotos(flag)

+

Setting autoload download photos

+
+

Parameter

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

flag

+
+

boolean

+
+

 

+
+

true/false

+
+
+
+
+
async
+

setAutoDownloadVideos(flag)

+

Setting autoload download videos

+
+

Parameter

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

flag

+
+

boolean

+
+

 

+
+

true/false

+
+
+
+
+
async

setDisplayName(displayName) → Promise containing Boolean

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

@@ -2267,7 +2620,7 @@

Parameter

reason

-

(WAState or "NAVIGATION")

+

(WAState or "LOGOUT")

 

@@ -2772,6 +3125,38 @@

Parameters

+

message_ciphertext

+

Emitted when messages are edited

+
+

Parameter

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

message

+
+

Message

+
+

 

+
+
+
+
+

message_create

Emitted when a new message is created, which may include the current user's own messages.

@@ -3155,6 +3540,11 @@

ready

Emitted when the client has initialized and is ready to receive messages.

+

vote_update

+

Emitted when some poll option is selected or deselected, + shows a user's current selected option(s) on the poll

+
+
@@ -3165,7 +3555,7 @@

ready

diff --git a/docs/Client.js.html b/docs/Client.js.html index 0fe7276791..ec7519e1ef 100644 --- a/docs/Client.js.html +++ b/docs/Client.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: Client.js + whatsapp-web.js 1.26.0 » Source: Client.js @@ -15,7 +15,7 @@ @@ -38,12 +38,15 @@

Source: Client.js

const Util = require('./util/Util'); const InterfaceController = require('./util/InterfaceController'); const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants'); -const { ExposeStore, LoadUtils } = require('./util/Injected'); +const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore'); +const { ExposeStore } = require('./util/Injected/Store'); +const { ExposeLegacyAuthStore } = require('./util/Injected/AuthStore/LegacyAuthStore'); +const { ExposeLegacyStore } = require('./util/Injected/LegacyStore'); +const { LoadUtils } = require('./util/Injected/Utils'); const ChatFactory = require('./factories/ChatFactory'); const ContactFactory = require('./factories/ContactFactory'); 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 { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures'); const NoAuth = require('./authStrategies/NoAuth'); /** @@ -61,7 +64,7 @@

Source: Client.js

* @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session * @param {string} options.userAgent - User agent to use in puppeteer - * @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers + * @param {string} options.ffmpegPath - Ffmpeg path to use when formatting 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. * @@ -74,6 +77,8 @@

Source: Client.js

* @fires Client#message_create * @fires Client#message_revoke_me * @fires Client#message_revoke_everyone + * @fires Client#message_ciphertext + * @fires Client#message_edit * @fires Client#media_uploaded * @fires Client#group_join * @fires Client#group_leave @@ -83,6 +88,7 @@

Source: Client.js

* @fires Client#contact_changed * @fires Client#group_admin_changed * @fires Client#group_membership_request + * @fires Client#vote_update */ class Client extends EventEmitter { constructor(options = {}) { @@ -91,37 +97,227 @@

Source: Client.js

this.options = Util.mergeDefault(DefaultOptions, options); if(!this.options.authStrategy) { - if(Object.prototype.hasOwnProperty.call(this.options, 'session')) { - process.emitWarning( - 'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' + - 'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).', - 'DeprecationWarning' - ); - - this.authStrategy = new LegacySessionAuth({ - session: this.options.session, - restartOnAuthFail: this.options.restartOnAuthFail - }); - } else { - this.authStrategy = new NoAuth(); - } + this.authStrategy = new NoAuth(); } else { this.authStrategy = this.options.authStrategy; } this.authStrategy.setup(this); + /** + * @type {puppeteer.Browser} + */ this.pupBrowser = null; + /** + * @type {puppeteer.Page} + */ this.pupPage = null; + this.currentIndexHtml = null; + this.lastLoggedOut = false; + Util.setFfmpegPath(this.options.ffmpegPath); } + /** + * Injection logic + * Private function + * @property {boolean} reinject is this a reinject? + */ + async inject(reinject = false) { + await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs}); + + const version = await this.getWWebVersion(); + const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000; + + if (isCometOrAbove) { + await this.pupPage.evaluate(ExposeAuthStore); + } else { + await this.pupPage.evaluate(ExposeLegacyAuthStore, moduleRaid.toString()); + } + + const needAuthentication = await this.pupPage.evaluate(async () => { + let state = window.AuthStore.AppState.state; + + if (state === 'OPENING' || state === 'UNLAUNCHED' || state === 'PAIRING') { + // wait till state changes + await new Promise(r => { + window.AuthStore.AppState.on('change:state', function waitTillInit(_AppState, state) { + if (state !== 'OPENING' &amp;&amp; state !== 'UNLAUNCHED' &amp;&amp; state !== 'PAIRING') { + window.AuthStore.AppState.off('change:state', waitTillInit); + r(); + } + }); + }); + } + state = window.AuthStore.AppState.state; + return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE'; + }); + + if (needAuthentication) { + const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); + + if(failed) { + /** + * Emitted when there has been an error while trying to restore an existing session + * @event Client#auth_failure + * @param {string} message + */ + this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload); + await this.destroy(); + if (restart) { + // session restore failed so try again but without session to force new authentication + return this.initialize(); + } + return; + } + + // Register qr events + let qrRetries = 0; + const injected = await this.pupPage.evaluate(() => { + return typeof window.onQRChangedEvent !== 'undefined'; + }); + if (!injected) { + await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => { + /** + * Emitted when a QR code is received + * @event Client#qr + * @param {string} qr QR Code + */ + this.emit(Events.QR_RECEIVED, qr); + if (this.options.qrMaxRetries > 0) { + qrRetries++; + if (qrRetries > this.options.qrMaxRetries) { + this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); + await this.destroy(); + } + } + }); + } + + + await this.pupPage.evaluate(async () => { + const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); + const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get(); + const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey); + const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey); + const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey(); + const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; + const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform; + + window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr + window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes + }); + } + + if (!reinject) { + await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => { + if (state == 'UNPAIRED_IDLE') { + // refresh qr code + window.Store.Cmd.refreshQR(); + } + }); + + await this.pupPage.exposeFunction('onAppStateHasSyncedEvent', async () => { + const authEventPayload = await this.authStrategy.getAuthEventPayload(); + /** + * Emitted when authentication is successful + * @event Client#authenticated + */ + this.emit(Events.AUTHENTICATED, authEventPayload); + + const injected = await this.pupPage.evaluate(async () => { + return typeof window.Store !== 'undefined' &amp;&amp; typeof window.WWebJS !== 'undefined'; + }); + + if (!injected) { + if (this.options.webVersionCache.type === 'local' &amp;&amp; this.currentIndexHtml) { + const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; + const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions); + + await webCache.persist(this.currentIndexHtml, version); + } + + if (isCometOrAbove) { + await this.pupPage.evaluate(ExposeStore); + } else { + // make sure all modules are ready before injection + // 2 second delay after authentication makes sense and does not need to be made dyanmic or removed + await new Promise(r => setTimeout(r, 2000)); + await this.pupPage.evaluate(ExposeLegacyStore); + } + + // Check window.Store Injection + await this.pupPage.waitForFunction('window.Store != undefined'); + + /** + * Current connection information + * @type {ClientInfo} + */ + this.info = new ClientInfo(this, await this.pupPage.evaluate(() => { + return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; + })); + + this.interface = new InterfaceController(this); + + //Load util functions (serializers, helper functions) + await this.pupPage.evaluate(LoadUtils); + + await this.attachEventListeners(reinject); + reinject = true; + } + /** + * Emitted when the client has initialized and is ready to receive messages. + * @event Client#ready + */ + this.emit(Events.READY); + this.authStrategy.afterAuthReady(); + }); + let lastPercent = null; + await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => { + if (lastPercent !== percent) { + lastPercent = percent; + this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now + } + }); + } + const logoutCatchInjected = await this.pupPage.evaluate(() => { + return typeof window.onLogoutEvent !== 'undefined'; + }); + if (!logoutCatchInjected) { + await this.pupPage.exposeFunction('onLogoutEvent', async () => { + this.lastLoggedOut = true; + await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _); + }); + } + await this.pupPage.evaluate(() => { + window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); }); + window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); }); + window.AuthStore.Cmd.on('offline_progress_update', () => { + window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress()); + }); + window.AuthStore.Cmd.on('logout', async () => { + await window.onLogoutEvent(); + }); + }); + } /** * Sets up events and requirements, kicks off authentication request */ async initialize() { - let [browser, page] = [null, null]; + + let + /** + * @type {puppeteer.Browser} + */ + browser, + /** + * @type {puppeteer.Page} + */ + page; + + browser = null; + page = null; await this.authStrategy.beforeBrowserInitialized(); @@ -134,6 +330,8 @@

Source: Client.js

if(!browserArgs.find(arg => arg.includes('--user-agent'))) { browserArgs.push(`--user-agent=${this.options.userAgent}`); } + // navigator.webdriver fix + browserArgs.push('--disable-blink-features=AutomationControlled'); browser = await puppeteer.launch({...puppeteerOpts, args: browserArgs}); page = (await browser.pages())[0]; @@ -152,551 +350,391 @@

Source: Client.js

await this.authStrategy.afterBrowserInitialized(); await this.initWebVersionCache(); + // ocVersion (isOfficialClient patch) + // remove after 2.3000.x hard release + await page.evaluateOnNewDocument(() => { + const originalError = Error; + window.originalError = originalError; + //eslint-disable-next-line no-global-assign + Error = function (message) { + const error = new originalError(message); + const originalStack = error.stack; + if (error.stack.includes('moduleRaid')) error.stack = originalStack + '\n at https://web.whatsapp.com/vendors~lazy_loaded_low_priority_components.05e98054dbd60f980427.js:2:44'; + return error; + }; + }); + await page.goto(WhatsWebURL, { waitUntil: 'load', timeout: 0, referer: 'https://whatsapp.com/' }); - 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 this.inject(); - await page.exposeFunction('loadingScreen', async (percent, message) => { - if (lastPercent !== percent || lastPercentMessage !== message) { - this.emit(Events.LOADING_SCREEN, percent, message); - lastPercent = percent; - lastPercentMessage = message; + this.pupPage.on('framenavigated', async (frame) => { + if(frame.url().includes('post_logout=1') || this.lastLoggedOut) { + this.emit(Events.DISCONNECTED, 'LOGOUT'); + await this.authStrategy.logout(); + await this.authStrategy.beforeBrowserInitialized(); + await this.authStrategy.afterBrowserInitialized(); + this.lastLoggedOut = false; } + await this.inject(true); }); + } - await page.evaluate( - async function (selectors) { - var observer = new MutationObserver(function () { - let progressBar = window.getElementByXpath( - selectors.PROGRESS - ); - let progressMessage = window.getElementByXpath( - selectors.PROGRESS_MESSAGE - ); + /** + * Request authentication via pairing code instead of QR code + * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) + * @param {boolean} showNotification - Show notification to pair on phone number + * @returns {Promise&lt;string>} - Returns a pairing code in format "ABCDEFGH" + */ + async requestPairingCode(phoneNumber, showNotification = true) { + return await this.pupPage.evaluate(async (phoneNumber, showNotification) => { + window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); + await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); + return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); + }, phoneNumber, showNotification); + } - if (progressBar) { - window.loadingScreen( - progressBar.value, - progressMessage.innerText - ); + /** + * Attach event listeners to WA Web + * Private function + * @property {boolean} reinject is this a reinject? + */ + async attachEventListeners(reinject = false) { + if (!reinject) { + await this.pupPage.exposeFunction('onAddMessageEvent', msg => { + if (msg.type === 'gp2') { + const notification = new GroupNotification(this, msg); + 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 + * @param {GroupNotification} notification GroupNotification with more information about the action + */ + this.emit(Events.GROUP_JOIN, notification); + } else if (msg.subtype === 'remove' || msg.subtype === 'leave') { + /** + * Emitted when a user leaves the chat or is removed by an admin. + * @event Client#group_leave + * @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 === 'membership_approval_request') { + /** + * 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. + * @event Client#group_update + * @param {GroupNotification} notification GroupNotification with more information about the action + */ + this.emit(Events.GROUP_UPDATE, notification); } - }); - - 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 - const needAuthentication = await Promise.race([ - new Promise(resolve => { - page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs }) - .then(() => resolve(false)) - .catch((err) => resolve(err)); - }), - new Promise(resolve => { - page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs }) - .then(() => resolve(true)) - .catch((err) => resolve(err)); - }) - ]); - - // Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race; - if (needAuthentication instanceof Error) throw needAuthentication; - - // Scan-qrcode selector was found. Needs authentication - if (needAuthentication) { - const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); - if(failed) { - /** - * Emitted when there has been an error while trying to restore an existing session - * @event Client#auth_failure - * @param {string} message - */ - this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload); - await this.destroy(); - if (restart) { - // session restore failed so try again but without session to force new authentication - return this.initialize(); + return; } - return; - } - const QR_CONTAINER = 'div[data-ref]'; - const QR_RETRY_BUTTON = 'div[data-ref] > span > button'; - let qrRetries = 0; - await page.exposeFunction('qrChanged', async (qr) => { + const message = new Message(this, msg); + /** - * Emitted when a QR code is received - * @event Client#qr - * @param {string} qr QR Code - */ - this.emit(Events.QR_RECEIVED, qr); - if (this.options.qrMaxRetries > 0) { - qrRetries++; - if (qrRetries > this.options.qrMaxRetries) { - this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); - await this.destroy(); - } - } - }); + * Emitted when a new message is created, which may include the current user's own messages. + * @event Client#message_create + * @param {Message} message The message that was created + */ + this.emit(Events.MESSAGE_CREATE, message); - await page.evaluate(function (selectors) { - const qr_container = document.querySelector(selectors.QR_CONTAINER); - window.qrChanged(qr_container.dataset.ref); + if (msg.id.fromMe) return; - const obs = new MutationObserver((muts) => { - muts.forEach(mut => { - // Listens to qr token change - if (mut.type === 'attributes' &amp;&amp; mut.attributeName === 'data-ref') { - window.qrChanged(mut.target.dataset.ref); - } - // Listens to retry button, when found, click it - else if (mut.type === 'childList') { - const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON); - if (retry_button) retry_button.click(); - } - }); - }); - obs.observe(qr_container.parentElement, { - subtree: true, - childList: true, - attributes: true, - attributeFilter: ['data-ref'], - }); - }, { - QR_CONTAINER, - QR_RETRY_BUTTON + /** + * Emitted when a new message is received. + * @event Client#message + * @param {Message} message The message that was received + */ + this.emit(Events.MESSAGE_RECEIVED, message); }); - // Wait for code scan - try { - await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 }); - } catch(error) { - if ( - error.name === 'ProtocolError' &amp;&amp; - error.message &amp;&amp; - error.message.match(/Target closed/) - ) { - // something has called .destroy() while waiting - return; - } + let last_message; - throw error; - } - - } + await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => { - 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 (!['>', '>=', '&lt;', '&lt;=', '='].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$/, ''); + if (msg.type === 'revoked') { + const message = new Message(this, msg); + let revoked_msg; + if (last_message &amp;&amp; msg.id.id === last_message.id.id) { + revoked_msg = new Message(this, last_message); + } - while (lOperand.length !== rOperand.length) { - lOperand.length > rOperand.length - ? rOperand = rOperand.concat('0') - : lOperand = lOperand.concat('0'); + /** + * Emitted when a message is deleted for everyone in the chat. + * @event Client#message_revoke_everyone + * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data. + * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data. + * Note that due to the way this data is captured, it may be possible that this param will be undefined. + */ + this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); } - lOperand = Number(lOperand.replace(/\./g, '')); - rOperand = Number(rOperand.replace(/\./g, '')); + }); - return ( - operator === '>' ? lOperand > rOperand : - operator === '>=' ? lOperand >= rOperand : - operator === '&lt;' ? lOperand &lt; rOperand : - operator === '&lt;=' ? lOperand &lt;= rOperand : - operator === '=' ? lOperand === rOperand : - false - ); - }; - }); + await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => { - await page.evaluate(ExposeStore, moduleRaid.toString()); - const authEventPayload = await this.authStrategy.getAuthEventPayload(); + if (msg.type !== 'revoked') { + last_message = msg; + } - /** - * Emitted when authentication is successful - * @event Client#authenticated - */ - this.emit(Events.AUTHENTICATED, authEventPayload); + /** + * The event notification that is received when one of + * the group participants changes their phone number. + */ + const isParticipant = msg.type === 'gp2' &amp;&amp; msg.subtype === 'modify'; - // Check window.Store Injection - await page.waitForFunction('window.Store != undefined'); + /** + * The event notification that is received when one of + * the contacts changes their phone number. + */ + const isContact = msg.type === 'notification_template' &amp;&amp; msg.subtype === 'change_number'; - await page.evaluate(async () => { - // safely unregister service workers - const registrations = await navigator.serviceWorker.getRegistrations(); - for (let registration of registrations) { - registration.unregister(); - } - }); + 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); - //Load util functions (serializers, helper functions) - await page.evaluate(LoadUtils); + const newId = isParticipant ? msg.recipients[0] : msg.to; + const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId); - // Expose client info - /** - * Current connection information - * @type {ClientInfo} - */ - this.info = new ClientInfo(this, await page.evaluate(() => { - return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; - })); - - // Add InterfaceController - this.interface = new InterfaceController(this); - - // Register events - await page.exposeFunction('onAddMessageEvent', msg => { - if (msg.type === 'gp2') { - const notification = new GroupNotification(this, msg); - 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 - * @param {GroupNotification} notification GroupNotification with more information about the action - */ - this.emit(Events.GROUP_JOIN, notification); - } else if (msg.subtype === 'remove' || msg.subtype === 'leave') { /** - * Emitted when a user leaves the chat or is removed by an admin. - * @event Client#group_leave - * @param {GroupNotification} notification GroupNotification with more information about the action + * 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.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. - * @event Client#group_update - * @param {GroupNotification} notification GroupNotification with more information about the action - */ - this.emit(Events.GROUP_UPDATE, notification); + this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact); } - return; - } - - const message = new Message(this, msg); - - /** - * Emitted when a new message is created, which may include the current user's own messages. - * @event Client#message_create - * @param {Message} message The message that was created - */ - this.emit(Events.MESSAGE_CREATE, message); - - if (msg.id.fromMe) return; - - /** - * Emitted when a new message is received. - * @event Client#message - * @param {Message} message The message that was received - */ - this.emit(Events.MESSAGE_RECEIVED, message); - }); + }); - let last_message; + await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => { - await page.exposeFunction('onChangeMessageTypeEvent', (msg) => { + if (!msg.isNewMsg) return; - if (msg.type === 'revoked') { const message = new Message(this, msg); - let revoked_msg; - if (last_message &amp;&amp; msg.id.id === last_message.id.id) { - revoked_msg = new Message(this, last_message); - } /** - * Emitted when a message is deleted for everyone in the chat. - * @event Client#message_revoke_everyone - * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data. - * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data. - * Note that due to the way this data is captured, it may be possible that this param will be undefined. + * Emitted when a message is deleted by the current user. + * @event Client#message_revoke_me + * @param {Message} message The message that was revoked */ - this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); - } + this.emit(Events.MESSAGE_REVOKED_ME, message); - }); - - await page.exposeFunction('onChangeMessageEvent', (msg) => { - - if (msg.type !== 'revoked') { - last_message = msg; - } - - /** - * The event notification that is received when one of - * the group participants changes their phone number. - */ - const isParticipant = msg.type === 'gp2' &amp;&amp; msg.subtype === 'modify'; + }); - /** - * The event notification that is received when one of - * the contacts changes their phone number. - */ - const isContact = msg.type === 'notification_template' &amp;&amp; msg.subtype === 'change_number'; + await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => { - 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. + * Emitted when an ack event occurrs on message type. + * @event Client#message_ack + * @param {Message} message The message that was affected + * @param {MessageAck} ack The new ACK value */ - this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact); - } - }); - - await page.exposeFunction('onRemoveMessageEvent', (msg) => { - - if (!msg.isNewMsg) return; - - const message = new Message(this, msg); - - /** - * Emitted when a message is deleted by the current user. - * @event Client#message_revoke_me - * @param {Message} message The message that was revoked - */ - this.emit(Events.MESSAGE_REVOKED_ME, message); - - }); - - await page.exposeFunction('onMessageAckEvent', (msg, ack) => { + this.emit(Events.MESSAGE_ACK, message, ack); - const message = new Message(this, msg); + }); - /** - * Emitted when an ack event occurrs on message type. - * @event Client#message_ack - * @param {Message} message The message that was affected - * @param {MessageAck} ack The new ACK value - */ - this.emit(Events.MESSAGE_ACK, message, ack); + await this.pupPage.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 this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => { - 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); - }); + const message = new Message(this, msg); - await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => { + /** + * Emitted when media has been uploaded for a message sent by the client. + * @event Client#media_uploaded + * @param {Message} message The message with media that was uploaded + */ + this.emit(Events.MEDIA_UPLOADED, message); + }); - const message = new Message(this, msg); + await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => { + /** + * Emitted when the connection state changes + * @event Client#change_state + * @param {WAState} state the new connection state + */ + this.emit(Events.STATE_CHANGED, state); - /** - * Emitted when media has been uploaded for a message sent by the client. - * @event Client#media_uploaded - * @param {Message} message The message with media that was uploaded - */ - this.emit(Events.MEDIA_UPLOADED, message); - }); + const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT]; - await page.exposeFunction('onAppStateChangedEvent', async (state) => { + if (this.options.takeoverOnConflict) { + ACCEPTED_STATES.push(WAState.CONFLICT); - /** - * Emitted when the connection state changes - * @event Client#change_state - * @param {WAState} state the new connection state - */ - this.emit(Events.STATE_CHANGED, state); + if (state === WAState.CONFLICT) { + setTimeout(() => { + this.pupPage.evaluate(() => window.Store.AppState.takeover()); + }, this.options.takeoverTimeoutMs); + } + } - const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT]; + if (!ACCEPTED_STATES.includes(state)) { + /** + * Emitted when the client has been disconnected + * @event Client#disconnected + * @param {WAState|"LOGOUT"} reason reason that caused the disconnect + */ + await this.authStrategy.disconnect(); + this.emit(Events.DISCONNECTED, state); + this.destroy(); + } + }); - if (this.options.takeoverOnConflict) { - ACCEPTED_STATES.push(WAState.CONFLICT); + await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => { + const { battery, plugged } = state; - if (state === WAState.CONFLICT) { - setTimeout(() => { - this.pupPage.evaluate(() => window.Store.AppState.takeover()); - }, this.options.takeoverTimeoutMs); - } - } + if (battery === undefined) return; - if (!ACCEPTED_STATES.includes(state)) { /** - * Emitted when the client has been disconnected - * @event Client#disconnected - * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect + * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. + * @event Client#change_battery + * @param {object} batteryInfo + * @param {number} batteryInfo.battery - The current battery percentage + * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) + * @deprecated */ - await this.authStrategy.disconnect(); - this.emit(Events.DISCONNECTED, state); - this.destroy(); - } - }); + this.emit(Events.BATTERY_CHANGED, { battery, plugged }); + }); - await page.exposeFunction('onBatteryStateChangedEvent', (state) => { - const { battery, plugged } = state; + await this.pupPage.exposeFunction('onIncomingCall', (call) => { + /** + * Emitted when a call is received + * @event Client#incoming_call + * @param {object} call + * @param {number} call.id - Call id + * @param {string} call.peerJid - Who called + * @param {boolean} call.isVideo - if is video + * @param {boolean} call.isGroup - if is group + * @param {boolean} call.canHandleLocally - if we can handle in waweb + * @param {boolean} call.outgoing - if is outgoing + * @param {boolean} call.webClientShouldHandle - If Waweb should handle + * @param {object} call.participants - Participants + */ + const cll = new Call(this, call); + this.emit(Events.INCOMING_CALL, cll); + }); - if (battery === undefined) return; + await this.pupPage.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 + */ - /** - * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. - * @event Client#change_battery - * @param {object} batteryInfo - * @param {number} batteryInfo.battery - The current battery percentage - * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) - * @deprecated - */ - this.emit(Events.BATTERY_CHANGED, { battery, plugged }); - }); + this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction)); + } + }); - await page.exposeFunction('onIncomingCall', (call) => { - /** - * Emitted when a call is received - * @event Client#incoming_call - * @param {object} call - * @param {number} call.id - Call id - * @param {string} call.peerJid - Who called - * @param {boolean} call.isVideo - if is video - * @param {boolean} call.isGroup - if is group - * @param {boolean} call.canHandleLocally - if we can handle in waweb - * @param {boolean} call.outgoing - if is outgoing - * @param {boolean} call.webClientShouldHandle - If Waweb should handle - * @param {object} call.participants - Participants - */ - const cll = new Call(this, call); - this.emit(Events.INCOMING_CALL, cll); - }); + await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => { + const _chat = await this.getChatById(chat.id); - 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 + * Emitted when a chat is removed + * @event Client#chat_removed + * @param {Chat} chat */ - - 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); + this.emit(Events.CHAT_REMOVED, _chat); + }); - /** - * 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 this.pupPage.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) => { + await this.pupPage.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); + }); - if(msg.type === 'revoked'){ - return; - } + await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => { + + /** + * Emitted when messages are edited + * @event Client#message_ciphertext + * @param {Message} message + */ + this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); + }); + } + + await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => { + const _vote = new PollVote(this, vote); /** - * Emitted when messages are edited - * @event Client#message_edit - * @param {Message} message - * @param {string} newBody - * @param {string} prevBody + * Emitted when some poll option is selected or deselected, + * shows a user's current selected option(s) on the poll + * @event Client#vote_update */ - this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); + this.emit(Events.VOTE_UPDATE, _vote); }); - await page.evaluate(() => { + await this.pupPage.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 &amp;&amp; !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); }); - window.Store.Msg.on('change:body', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); }); + window.Store.Msg.on('change:body change:caption', (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); }); @@ -707,14 +745,35 @@

Source: Client.js

if(msg.type === 'ciphertext') { // defer message event until ciphertext is resolved (type changed) msg.once('change:type', (_msg) => window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg))); + window.onAddMessageCiphertextEvent(window.WWebJS.getMessageModel(msg)); } else { window.onAddMessageEvent(window.WWebJS.getMessageModel(msg)); } } }); window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);}); + window.Store.PollVote.on('add', async (vote) => { + const pollVoteModel = await window.WWebJS.getPollVoteModel(vote); + pollVoteModel &amp;&amp; window.onPollVoteEvent(pollVoteModel); + }); - { + if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) { + const module = window.Store.AddonReactionTable; + const ogMethod = module.bulkUpsert; + module.bulkUpsert = ((...args) => { + window.onReaction(args[0].map(reaction => { + const msgKey = reaction.id; + const parentMsgKey = reaction.reactionParentKey; + const timestamp = reaction.reactionTimestamp / 1000; + const sender = reaction.author ?? reaction.from; + const senderUserJid = sender._serialized; + + return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp }; + })); + + return ogMethod(...args); + }).bind(module); + } else { const module = window.Store.createOrUpdateReactionsModule; const ogMethod = module.createOrUpdateReactions; module.createOrUpdateReactions = ((...args) => { @@ -730,24 +789,7 @@

Source: Client.js

}).bind(module); } }); - - /** - * Emitted when the client has initialized and is ready to receive messages. - * @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; @@ -772,7 +814,8 @@

Source: Client.js

} else { this.pupPage.on('response', async (res) => { if(res.ok() &amp;&amp; res.url() === WhatsWebURL) { - await webCache.persist(await res.text()); + const indexHtml = await res.text(); + this.currentIndexHtml = indexHtml; } }); } @@ -791,7 +834,9 @@

Source: Client.js

*/ async logout() { await this.pupPage.evaluate(() => { - return window.Store.AppState.logout(); + if (window.Store &amp;&amp; window.Store.AppState &amp;&amp; typeof window.Store.AppState.logout === 'function') { + return window.Store.AppState.logout(); + } }); await this.pupBrowser.close(); @@ -828,6 +873,13 @@

Source: Client.js

return result; } + /** + * An object representing mentions of groups + * @typedef {Object} GroupMention + * @property {string} subject - The name of a group to mention (can be custom) + * @property {string} id - The group ID, e.g.: 'XXXXXXXXXX@g.us' + */ + /** * Message options. * @typedef {Object} MessageSendOptions @@ -840,8 +892,10 @@

Source: Client.js

* @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) - * @property {Contact[]} [mentions] - Contacts that are being mentioned in the message + * @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions + * @property {string[]} [mentions] - User IDs to mention in the message * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message + * @property {string} [invokedBotWid=undefined] - Bot Wid when doing a bot mention like @Meta AI * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true). * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true). * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null. @@ -857,10 +911,16 @@

Source: Client.js

* @returns {Promise&lt;Message>} Message that was just sent */ async sendMessage(chatId, content, options = {}) { - if (options.mentions &amp;&amp; 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); + if (options.mentions) { + !Array.isArray(options.mentions) &amp;&amp; (options.mentions = [options.mentions]); + if (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); + } } + + options.groupMentions &amp;&amp; !Array.isArray(options.groupMentions) &amp;&amp; (options.groupMentions = [options.groupMentions]); + let internalOptions = { linkPreview: options.linkPreview === false ? undefined : true, sendAudioAsVoice: options.sendAudioAsVoice, @@ -869,8 +929,10 @@

Source: Client.js

sendMediaAsDocument: options.sendMediaAsDocument, caption: options.caption, quotedMessageId: options.quotedMessageId, - parseVCards: options.parseVCards === false ? false : true, - mentionedJidList: Array.isArray(options.mentions) ? options.mentions : [], + parseVCards: options.parseVCards !== false, + mentionedJidList: options.mentions || [], + groupMentions: options.groupMentions, + invokedBotWid: options.invokedBotWid, extraOptions: options.extra }; @@ -922,11 +984,11 @@

Source: Client.js

if (sendSeen) { - window.WWebJS.sendSeen(chatId); + await window.WWebJS.sendSeen(chatId); } const msg = await window.WWebJS.sendMessage(chat, message, options, sendSeen); - return msg.serialize(); + return window.WWebJS.getMessageModel(msg); }, chatId, content, internalOptions, sendSeen); return new Message(this, newMessage); @@ -1006,7 +1068,7 @@

Source: Client.js

if(msg) return window.WWebJS.getMessageModel(msg); const params = messageId.split('_'); - if(params.length !== 3) throw new Error('Invalid serialized message id specified'); + if (params.length !== 3 &amp;&amp; params.length !== 4) throw new Error('Invalid serialized message id specified'); let messagesObject = await window.Store.Msg.getMessagesById([messageId]); if (messagesObject &amp;&amp; messagesObject.messages.length) msg = messagesObject.messages[0]; @@ -1076,14 +1138,8 @@

Source: Client.js

async setDisplayName(displayName) { const couldSet = await this.pupPage.evaluate(async displayName => { if(!window.Store.Conn.canSetMyPushname()) return false; - - if(window.Store.MDBackend) { - // TODO - return false; - } else { - const res = await window.Store.Wap.setPushname(displayName); - return !res.status || res.status === 200; - } + await window.Store.Settings.setPushname(displayName); + return true; }, displayName); return couldSet; @@ -1224,7 +1280,9 @@

Source: Client.js

const profilePic = await this.pupPage.evaluate(async contactId => { try { const chatWid = window.Store.WidFactory.createWid(contactId); - return await window.Store.ProfilePic.profilePicFind(chatWid); + return window.compareWwebVersions(window.Debug.VERSION, '&lt;', '2.3000.0') + ? await window.Store.ProfilePic.profilePicFind(chatWid) + : await window.Store.ProfilePic.requestProfilePicFromServer(chatWid); } catch (err) { if(err.name === 'ServerStatusCodeError') return undefined; throw err; @@ -1390,10 +1448,18 @@

Source: Client.js

try { createGroupResult = await window.Store.GroupUtils.createGroup( - title, - participantWids, - messageTimer, - parentGroupWid + { + 'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode, + 'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode, + 'announce': options.announce === undefined ? true : options.announce, + 'ephemeralDuration': messageTimer, + 'full': undefined, + 'parentGroupId': parentGroupWid, + 'restrict': options.restrict === undefined ? true : options.restrict, + 'thumb': undefined, + 'title': title, + }, + participantWids ); } catch (err) { return 'CreateGroupError: An unknown error occupied while creating a group'; @@ -1405,7 +1471,7 @@

Source: Client.js

const statusCode = participant.error ?? 200; if (autoSendInviteV4 &amp;&amp; statusCode === 403) { - window.Store.ContactCollection.gadd(participant.wid, { silent: true }); + window.Store.Contact.gadd(participant.wid, { silent: true }); const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage( await window.Store.Chat.find(participant.wid), createGroupResult.wid._serialized, @@ -1582,8 +1648,8 @@

Source: Client.js

* @returns {Promise&lt;Array&lt;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 this.pupPage.evaluate(async (groupId) => { + const groupWid = window.Store.WidFactory.createWid(groupId); return await window.Store.MembershipRequestUtils.getMembershipApprovalRequests(groupWid); }, groupId); } @@ -1628,10 +1694,85 @@

Source: Client.js

return await window.WWebJS.membershipRequestAction(groupId, 'Reject', requesterIds, sleep); }, groupId, options); } + + + /** + * Setting autoload download audio + * @param {boolean} flag true/false + */ + async setAutoDownloadAudio(flag) { + await this.pupPage.evaluate(async flag => { + const autoDownload = window.Store.Settings.getAutoDownloadAudio(); + if (autoDownload === flag) { + return flag; + } + await window.Store.Settings.setAutoDownloadAudio(flag); + return flag; + }, flag); + } + + /** + * Setting autoload download documents + * @param {boolean} flag true/false + */ + async setAutoDownloadDocuments(flag) { + await this.pupPage.evaluate(async flag => { + const autoDownload = window.Store.Settings.getAutoDownloadDocuments(); + if (autoDownload === flag) { + return flag; + } + await window.Store.Settings.setAutoDownloadDocuments(flag); + return flag; + }, flag); + } + + /** + * Setting autoload download photos + * @param {boolean} flag true/false + */ + async setAutoDownloadPhotos(flag) { + await this.pupPage.evaluate(async flag => { + const autoDownload = window.Store.Settings.getAutoDownloadPhotos(); + if (autoDownload === flag) { + return flag; + } + await window.Store.Settings.setAutoDownloadPhotos(flag); + return flag; + }, flag); + } + + /** + * Setting autoload download videos + * @param {boolean} flag true/false + */ + async setAutoDownloadVideos(flag) { + await this.pupPage.evaluate(async flag => { + const autoDownload = window.Store.Settings.getAutoDownloadVideos(); + if (autoDownload === flag) { + return flag; + } + await window.Store.Settings.setAutoDownloadVideos(flag); + return flag; + }, flag); + } + + /** + * Get user device count by ID + * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one + * So for a non-enterprise user with one WaWeb connection it should return "2" + * @param {string} contactId + * @returns {number} + */ + async getContactDeviceCount(contactId) { + let devices = await window.Store.DeviceList.getDeviceIds([window.Store.WidFactory.createWid(contactId)]); + if(devices &amp;&amp; devices.length &amp;&amp; devices[0] != null &amp;&amp; typeof devices[0].devices == 'object'){ + return devices[0].devices.length; + } + return 0; + } } -module.exports = Client; - +module.exports = Client; @@ -1641,7 +1782,7 @@

Source: Client.js

diff --git a/docs/ClientInfo.html b/docs/ClientInfo.html index 015f210d86..df2c6aaec6 100644 --- a/docs/ClientInfo.html +++ b/docs/ClientInfo.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: ClientInfo + whatsapp-web.js 1.26.0 » Class: ClientInfo @@ -15,7 +15,7 @@ @@ -242,7 +242,7 @@

getBatteryStatus diff --git a/docs/Contact.html b/docs/Contact.html index 5a9f658598..fa564cb5f0 100644 --- a/docs/Contact.html +++ b/docs/Contact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Contact + whatsapp-web.js 1.26.0 » Class: Contact @@ -15,7 +15,7 @@ @@ -293,7 +293,7 @@

unblock diff --git a/docs/GroupChat.html b/docs/GroupChat.html index 37e2024f2e..f030c2f550 100644 --- a/docs/GroupChat.html +++ b/docs/GroupChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: GroupChat + whatsapp-web.js 1.26.0 » Class: GroupChat @@ -15,7 +15,7 @@ @@ -187,6 +187,9 @@

Methods

sendStateTyping()
+
setAddMembersAdminsOnly([adminsOnly])
+
+
setDescription(description)
@@ -930,6 +933,47 @@

sendStateTypingChat#sendStateTyping
async
+

setAddMembersAdminsOnly([adminsOnly]) → Promise containing boolean

+

Updates the group setting to allow only admins to add members to the group.

+
+

Parameter

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

adminsOnly

+
+

boolean

+
+

Yes

+
+

Enable or disable this option

+

Defaults to true.

+
+
+
+
Returns
+
+

Promise containing boolean  +

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

+

+
+
+
async

setDescription(description) → Promise containing boolean

Updates the group description

@@ -1163,7 +1207,7 @@

unpin diff --git a/docs/GroupNotification.html b/docs/GroupNotification.html index a66ec9356e..b9698d9a7a 100644 --- a/docs/GroupNotification.html +++ b/docs/GroupNotification.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: GroupNotification + whatsapp-web.js 1.26.0 » Class: GroupNotification @@ -15,7 +15,7 @@ @@ -233,7 +233,7 @@

Parameters

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

Parameter

diff --git a/docs/Label.html b/docs/Label.html index 8b3219a335..e3109ec53b 100644 --- a/docs/Label.html +++ b/docs/Label.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Label + whatsapp-web.js 1.26.0 » Class: Label @@ -15,7 +15,7 @@ @@ -163,7 +163,7 @@

getChats diff --git a/docs/List.html b/docs/List.html index 9135bdf07c..4a4d88c3d5 100644 --- a/docs/List.html +++ b/docs/List.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: List + whatsapp-web.js 1.26.0 » Class: List @@ -15,7 +15,7 @@ @@ -256,7 +256,7 @@

Parameter

diff --git a/docs/LocalAuth.html b/docs/LocalAuth.html index 81f524ff85..f33cae4ff9 100644 --- a/docs/LocalAuth.html +++ b/docs/LocalAuth.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: LocalAuth + whatsapp-web.js 1.26.0 » Class: LocalAuth @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@

Parameters

diff --git a/docs/LocalWebCache.html b/docs/LocalWebCache.html index bd21a4d3bb..7c8e972511 100644 --- a/docs/LocalWebCache.html +++ b/docs/LocalWebCache.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: LocalWebCache + whatsapp-web.js 1.26.0 » Class: LocalWebCache @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@

Parameters

diff --git a/docs/Location.html b/docs/Location.html index 588d5764c1..af24f450e8 100644 --- a/docs/Location.html +++ b/docs/Location.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Location + whatsapp-web.js 1.26.0 » Class: Location @@ -15,7 +15,7 @@ @@ -173,7 +173,7 @@

url
diff --git a/docs/Message.html b/docs/Message.html index 64420eccc4..063daef89b 100644 --- a/docs/Message.html +++ b/docs/Message.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Message + whatsapp-web.js 1.26.0 » Class: Message @@ -15,7 +15,7 @@ @@ -66,6 +66,9 @@

Properties

fromMe
+
groupMentions
+
+
hasMedia
@@ -103,13 +106,13 @@

Properties

links
+
location
+
+
-
location
-
-
mediaKey
@@ -164,11 +167,14 @@

Methods

getChat()
+
getContact()
+
+
-
getReactions()
+
pin(duration)
react(reaction)
@@ -205,6 +214,9 @@

Methods

star()
+
unpin()
+
+
unstar()
@@ -269,6 +281,11 @@

fromMe

+

groupMentions +  Array of GroupMention

+

Indicates whether there are group mentions in the message body

+
+

hasMedia  boolean

Indicates if the message has media available for download

@@ -335,7 +352,7 @@

mediaKey

mentionedIds -  Array of string

+  Array of Mention

Indicates the mentions in the message body.

@@ -538,8 +555,16 @@

getContactasync

+

getGroupMentions()

+

Returns groups mentioned in this message

+
+
Returns
+
+
+
async

getInfo() → Promise containing nullable MessageInfo

-

Get information about message delivery status. May return null if the message does not exist or is not sent by you.

+

Get information about message delivery status. + May return null if the message does not exist or is not sent by you.

Returns
@@ -592,6 +617,46 @@

getReactionsasync +

pin(duration) → Promise containing boolean

+

Pins the message (group admins can pin messages of all group members)

+
+

Parameter

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

duration

+
+

number

+
+

 

+
+

The duration in seconds the message will be pinned in a chat

+
+
+
+
Returns
+
+

Promise containing boolean  +

Returns true if the operation completed successfully, false otherwise

+

+
+
+
async

react(reaction) → Promise

React to this message with an emoji

@@ -710,6 +775,17 @@

star

async
+

unpin() → Promise containing boolean

+

Unpins the message (group admins can unpin messages of all group members)

+
+
Returns
+
+

Promise containing boolean  +

Returns true if the operation completed successfully, false otherwise

+

+
+
+
async

unstar()

Unstars this message

@@ -724,7 +800,7 @@

unstar<
diff --git a/docs/MessageMedia.html b/docs/MessageMedia.html index 4b58ccbe24..80d2104e9f 100644 --- a/docs/MessageMedia.html +++ b/docs/MessageMedia.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: MessageMedia + whatsapp-web.js 1.26.0 » Class: MessageMedia @@ -15,7 +15,7 @@ @@ -364,7 +364,7 @@

Parameters

diff --git a/docs/NoAuth.html b/docs/NoAuth.html index 36f7a61379..913f14c8d1 100644 --- a/docs/NoAuth.html +++ b/docs/NoAuth.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: NoAuth + whatsapp-web.js 1.26.0 » Class: NoAuth @@ -15,7 +15,7 @@ @@ -51,7 +51,7 @@

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

total diff --git a/docs/Poll.html b/docs/Poll.html index ba996d6dee..56eab4d9d4 100644 --- a/docs/Poll.html +++ b/docs/Poll.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Poll + whatsapp-web.js 1.26.0 » Class: Poll @@ -15,7 +15,7 @@ @@ -148,7 +148,7 @@

pollOptions diff --git a/docs/PollVote.html b/docs/PollVote.html new file mode 100644 index 0000000000..2516a2adb9 --- /dev/null +++ b/docs/PollVote.html @@ -0,0 +1,119 @@ + + + + + + + whatsapp-web.js 1.26.0 » Class: PollVote + + + + + + + + +
+
+
+
+ +
+
+

Properties

+
+
+
+
interractedAtTs
+
+
+
parentMessage
+
+
+
+
+
+
+
selectedOptions
+
+
+
voter
+
+
+
+
+
+
+
+
+
+
+

new PollVote()

+
+
Extends
+
Base
+
+
+
+

Properties

+
+

interractedAtTs +  number

+

Timestamp the option was selected or deselected at

+
+
+

parentMessage +  Message

+

The poll creation message associated with the poll vote

+
+
+

selectedOptions +  Array of SelectedPollOption

+

The selected poll option(s) + If it's an empty array, the user hasn't selected any options on the poll, + may occur when they deselected all poll options

+
+
+

voter +  string

+

The person who voted

+
+
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/PrivateChat.html b/docs/PrivateChat.html index d708426bc9..ed4e95c92f 100644 --- a/docs/PrivateChat.html +++ b/docs/PrivateChat.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: PrivateChat + whatsapp-web.js 1.26.0 » Class: PrivateChat @@ -15,7 +15,7 @@ @@ -583,7 +583,7 @@

unpin diff --git a/docs/PrivateContact.html b/docs/PrivateContact.html index a1c8aeb4b0..a4a76a77ba 100644 --- a/docs/PrivateContact.html +++ b/docs/PrivateContact.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: PrivateContact + whatsapp-web.js 1.26.0 » Class: PrivateContact @@ -15,7 +15,7 @@ @@ -319,7 +319,7 @@

unblock diff --git a/docs/Product.html b/docs/Product.html index 510c8aa6a6..40dfb48dd7 100644 --- a/docs/Product.html +++ b/docs/Product.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Product + whatsapp-web.js 1.26.0 » Class: Product @@ -15,7 +15,7 @@ @@ -127,7 +127,7 @@

thumbnailUrl diff --git a/docs/Reaction.html b/docs/Reaction.html index 510a7220e3..6848cbf0df 100644 --- a/docs/Reaction.html +++ b/docs/Reaction.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Reaction + whatsapp-web.js 1.26.0 » Class: Reaction @@ -15,7 +15,7 @@ @@ -144,7 +144,7 @@

timestamp diff --git a/docs/RemoteAuth.html b/docs/RemoteAuth.html index d55ea497f4..2a51db1123 100644 --- a/docs/RemoteAuth.html +++ b/docs/RemoteAuth.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: RemoteAuth + whatsapp-web.js 1.26.0 » Class: RemoteAuth @@ -15,7 +15,7 @@ @@ -148,7 +148,7 @@

Parameters

diff --git a/docs/RemoteWebCache.html b/docs/RemoteWebCache.html index b13924890d..0ac8c8759b 100644 --- a/docs/RemoteWebCache.html +++ b/docs/RemoteWebCache.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: RemoteWebCache + whatsapp-web.js 1.26.0 » Class: RemoteWebCache @@ -15,7 +15,7 @@ @@ -120,7 +120,7 @@

Parameters

diff --git a/docs/Util.html b/docs/Util.html index 9208ce1fca..2ee6d46b26 100644 --- a/docs/Util.html +++ b/docs/Util.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: Util + whatsapp-web.js 1.26.0 » Class: Util @@ -15,7 +15,7 @@ @@ -243,7 +243,7 @@

Parameter

diff --git a/docs/WebCache.html b/docs/WebCache.html index 3acfa732b8..deb2656f9e 100644 --- a/docs/WebCache.html +++ b/docs/WebCache.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Class: WebCache + whatsapp-web.js 1.26.0 » Class: WebCache @@ -15,7 +15,7 @@ @@ -50,7 +50,7 @@

new WebCache diff --git a/docs/authStrategies_BaseAuthStrategy.js.html b/docs/authStrategies_BaseAuthStrategy.js.html index b978fd0b55..7e3087dc59 100644 --- a/docs/authStrategies_BaseAuthStrategy.js.html +++ b/docs/authStrategies_BaseAuthStrategy.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: authStrategies/BaseAuthStrategy.js + whatsapp-web.js 1.26.0 » Source: authStrategies/BaseAuthStrategy.js @@ -15,7 +15,7 @@ @@ -65,7 +65,7 @@

Source: authStrategies/BaseAuthStrategy.js

diff --git a/docs/authStrategies_LocalAuth.js.html b/docs/authStrategies_LocalAuth.js.html index ab861875b0..1aba149ec6 100644 --- a/docs/authStrategies_LocalAuth.js.html +++ b/docs/authStrategies_LocalAuth.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: authStrategies/LocalAuth.js + whatsapp-web.js 1.26.0 » Source: authStrategies/LocalAuth.js @@ -15,7 +15,7 @@ @@ -75,7 +75,10 @@

Source: authStrategies/LocalAuth.js

async logout() { if (this.userDataDir) { - return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true, force: true }); + await fs.promises.rm(this.userDataDir, { recursive: true, force: true }) + .catch((e) => { + throw new Error(e); + }); } } @@ -92,7 +95,7 @@

Source: authStrategies/LocalAuth.js

diff --git a/docs/authStrategies_NoAuth.js.html b/docs/authStrategies_NoAuth.js.html index 60e4ecbba5..1870304cd9 100644 --- a/docs/authStrategies_NoAuth.js.html +++ b/docs/authStrategies_NoAuth.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: authStrategies/NoAuth.js + whatsapp-web.js 1.26.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 index 09bd8b2577..49f3cc689c 100644 --- a/docs/authStrategies_RemoteAuth.js.html +++ b/docs/authStrategies_RemoteAuth.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: authStrategies/RemoteAuth.js + whatsapp-web.js 1.26.0 » Source: authStrategies/RemoteAuth.js @@ -15,7 +15,7 @@ @@ -243,7 +243,7 @@

Source: authStrategies/RemoteAuth.js

diff --git a/docs/global.html b/docs/global.html index ef801936da..aaea4b3236 100644 --- a/docs/global.html +++ b/docs/global.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Globals + whatsapp-web.js 1.26.0 » Globals @@ -15,7 +15,7 @@ @@ -92,11 +92,17 @@

Abstract types

GroupMembershipRequest
+
GroupMembershipRequest
+
+

@@ -304,6 +319,19 @@

Properties

+ + +

MESSAGE_CIPHERTEXT

+ + +

 

+ + +

 

+ + + +

MESSAGE_CREATE

@@ -577,6 +605,19 @@

Properties

+ + +

VOTE_UPDATE

+ + +

 

+ + +

 

+ + + +

@@ -2213,6 +2254,153 @@

Properties

+

GroupMention +  Object

+

An object representing mentions of groups

+
+

Properties

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

subject

+
+

string

+
+

 

+
+

The name of a group to mention (can be custom)

+
+

id

+
+

string

+
+

 

+
+

The group ID, e.g.: 'XXXXXXXXXX@g.us'

+
+
+
+
+

GroupMention +  Object

+
+

Properties

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

groupSubject

+
+

string

+
+

 

+
+

The name of the group

+
+

groupJid

+
+

Object

+
+

 

+
+

The group ID

+

Values in groupJid have the following properties:

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

server

+
+

string

+
+

 

+
+
+

user

+
+

string

+
+

 

+
+
+

_serialized

+
+

string

+
+

 

+
+
+
+
+
+

GroupParticipant  Object

Group participant information

@@ -2554,6 +2742,64 @@

Properties

+

Mention +  Object

+
+

Properties

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

server

+
+

string

+
+

 

+
+
+

user

+
+

string

+
+

 

+
+
+

_serialized

+
+

string

+
+

 

+
+
+
+
+

MessageInfo  Object

Message Info

@@ -2806,18 +3052,32 @@

Properties

Id of the message that is being quoted (or replied to)

+ + +

groupMentions

+ + +

Array of GroupMention

+ + +

Yes

+ + +

An array of object that handle group mentions

+ +

mentions

-

Array of Contact

+

Array of string

Yes

-

Contacts that are being mentioned in the message

+

User IDs to mention in the message

@@ -2835,6 +3095,20 @@

Properties

Defaults to true.

+ + +

invokedBotWid

+ + +

string

+ + +

Yes

+ + +

Bot Wid when doing a bot mention like @Meta AI

+ +

stickerAuthor

@@ -3098,6 +3372,54 @@

Properties

+

SelectedPollOption +  Object

+

Selected poll option structure

+
+

Properties

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

id

+
+

number

+
+

 

+
+

The local selected or deselected option ID

+
+

name

+
+

string

+
+

 

+
+

The option name

+
+
+
+

StickerMetadata  Object

Sticker metadata.

@@ -3174,7 +3496,7 @@

Properties

-

moduleId

+

module

(string or number)

@@ -3202,7 +3524,55 @@

Properties

-

property

+

function

+ + +

string

+ + +

 

+ + +

The function name to get from a module

+ + + + + +
+
+

TargetOptions +  Object

+

Target options object description

+
+

Properties

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

module

+
+

(string or number)

+
+

 

+
+

The target module

+
+

function

string

@@ -3229,7 +3599,7 @@

Properties

diff --git a/docs/index.html b/docs/index.html index e29c2b6a8c..b07a22a6e5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,7 +3,7 @@ - whatsapp-web.js 1.23.0 » Home + whatsapp-web.js 1.26.0 » Home @@ -13,7 +13,7 @@ @@ -26,25 +26,64 @@
-

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

+
+
+

+ WWebJS Website +

+
+

+ npm + Depfu + WhatsApp_Web 2.2346.52 + Discord server +

+
+
+

About

+

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

+

The library works by launching the WhatsApp Web browser application and managing it using Puppeteer to create an instance of WhatsApp Web, thereby mitigating the risk of being blocked. The WhatsApp API client connects through the WhatsApp Web browser app, accessing its internal functions. This grants you access to nearly all the features available on WhatsApp Web, enabling dynamic handling similar to any other Node.js application.

+
+

[!IMPORTANT] +It is not guaranteed you will not be blocked by using this method. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe.

+
+

Links

Installation

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

-

Please note that Node v12+ is required.

+
+

[!NOTE] +Node v18+ is required.

+
+

QUICK STEPS TO UPGRADE NODE

+

Windows

+

Manual

+

Just get the latest LTS from the official node website.

+

npm

+
sudo npm install -g n
+sudo n stable
+
+

Choco

+
choco install nodejs-lts
+
+

Winget

+
winget install OpenJS.NodeJS.LTS
+
+

Ubuntu / Debian

+
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - &&\
+sudo apt-get install -y nodejs
+

Example usage

const { Client } = require('whatsapp-web.js');
 
@@ -67,8 +106,8 @@ 

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.

+

Take a look at example.js for another examples with additional use cases.
+For further details on saving and restoring sessions, explore the provided Authentication Strategies.

Supported features

@@ -96,7 +135,7 @@

Supported features

- + @@ -116,11 +155,11 @@

Supported features

- + - + @@ -163,6 +202,10 @@

Supported features

+ + + + @@ -186,11 +229,27 @@

Supported features

+ + + + + + + + + + + + + + + +
Send media (video)(requires google chrome)(requires Google Chrome)
Send stickers
Send buttons(DEPRECATED)
Send lists✅ (business accounts not supported)(DEPRECATED)
Receive location
Mention groups
Mute/unmute chats
React to messages
Create polls
Vote in polls🔜
Communities🔜
Channels🔜

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.

+

Feel free to open pull requests; we welcome contributions! However, for significant changes, it's best to open an issue beforehand. Make sure to review our contribution guidelines before creating a pull request. Before creating your own issue or pull request, always check to see if one already exists!

Supporting the project

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

    @@ -199,16 +258,16 @@

    Supporting the project

  • Sign up for DigitalOcean and get $200 in credit when you sign up (Referral)

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.

+

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 whatsapp.com. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners. Also it is not guaranteed you will not be blocked by using this method. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe.

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. +

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 +

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.

@@ -777,6 +836,11 @@

Client

+
+ Client#attachEventListeners() +
+
+
Client#createGroup(title, participants, options)
@@ -867,11 +931,6 @@

Client

- -
- -
-
Client#event:message
@@ -882,6 +941,16 @@

Client

+
+ Client#event:message_ciphertext +
+
+
+
+
+ +
+
Client#event:message_create
@@ -917,6 +986,11 @@

Client

+
+ Client#event:vote_update +
+
+
Client#getBlockedContacts()
@@ -952,6 +1026,11 @@

Client

+
+ Client#getContactDeviceCount(contactId) +
+
+
Client#getContacts()
@@ -992,11 +1071,6 @@

Client

-
-
- -
-
Client#getProfilePicUrl(contactId)
@@ -1017,11 +1091,21 @@

Client

+
+
+ + - - + +
+
Events.GROUP_MEMBERSHIP_REQUEST
@@ -1398,6 +1517,11 @@

Events

+
+ Events.MESSAGE_CIPHERTEXT +
+
+
Events.MESSAGE_CREATE
@@ -1408,16 +1532,16 @@

Events

-
-
- - + +
+
Events.MESSAGE_RECEIVED
@@ -1458,6 +1582,11 @@

Events

+
+ Events.VOTE_UPDATE +
+
+
@@ -1680,6 +1809,11 @@

GroupChat

+
+ GroupChat#setAddMembersAdminsOnly([adminsOnly]) +
+
+
GroupChat#setDescription(description)
@@ -2009,33 +2143,6 @@

Label

-
-
-

LegacySessionAuth

-
- - -
-
-
-
- -
-
-
-
- -
-
-

List

@@ -2295,6 +2402,11 @@

Message

+
+ Message#getGroupMentions() +
+
+
@@ -2330,6 +2442,11 @@

Message

+
+ Message#groupMentions +
+
+
Message#hasMedia
@@ -2410,6 +2527,11 @@

Message

+
+ Message#pin(duration) +
+
+
Message#rawData
@@ -2455,6 +2577,11 @@

Message

+
+ Message#unpin() +
+
+
Message#unstar()
@@ -2960,6 +3087,53 @@

Poll

+
+
+

PollVote

+
+
+
+
+ PollVote() +
+
+
+
+ PollVote#interractedAtTs +
+
+
+
+
+ + + +
+
+
+ PollVote#voter +
+
+
+
+
+ +
+
+

PrivateChat

@@ -3734,7 +3908,7 @@

window

diff --git a/docs/scripts/jsdoc-toc.js b/docs/scripts/jsdoc-toc.js index 80c357faee..a21d2ac0c3 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":"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":[]}], + 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":"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":"PollVote","id":"PollVote","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 dc4ee1f491..f5a09a22d4 100644 --- a/docs/structures_Base.js.html +++ b/docs/structures_Base.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Base.js + whatsapp-web.js 1.26.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 54b5345264..5d34a72817 100644 --- a/docs/structures_BusinessContact.js.html +++ b/docs/structures_BusinessContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/BusinessContact.js + whatsapp-web.js 1.26.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 0ccc18407a..626459905e 100644 --- a/docs/structures_Buttons.js.html +++ b/docs/structures_Buttons.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Buttons.js + whatsapp-web.js 1.26.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 089363cc81..c76c3ffcf5 100644 --- a/docs/structures_Call.js.html +++ b/docs/structures_Call.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Call.js + whatsapp-web.js 1.26.0 » Source: structures/Call.js @@ -15,7 +15,7 @@ @@ -114,7 +114,7 @@

Source: structures/Call.js

diff --git a/docs/structures_Chat.js.html b/docs/structures_Chat.js.html index c9dec6e2ed..22bea7b017 100644 --- a/docs/structures_Chat.js.html +++ b/docs/structures_Chat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Chat.js + whatsapp-web.js 1.26.0 » Source: structures/Chat.js @@ -15,7 +15,7 @@ @@ -314,7 +314,7 @@

Source: structures/Chat.js

diff --git a/docs/structures_ClientInfo.js.html b/docs/structures_ClientInfo.js.html index 4c19f3c7d9..2d7f831630 100644 --- a/docs/structures_ClientInfo.js.html +++ b/docs/structures_ClientInfo.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/ClientInfo.js + whatsapp-web.js 1.26.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 06a1e0aba9..58aaaf7c2d 100644 --- a/docs/structures_Contact.js.html +++ b/docs/structures_Contact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Contact.js + whatsapp-web.js 1.26.0 » Source: structures/Contact.js @@ -15,7 +15,7 @@ @@ -247,7 +247,7 @@

Source: structures/Contact.js

diff --git a/docs/structures_GroupChat.js.html b/docs/structures_GroupChat.js.html index 319c776520..03b4e35969 100644 --- a/docs/structures_GroupChat.js.html +++ b/docs/structures_GroupChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/GroupChat.js + whatsapp-web.js 1.26.0 » Source: structures/GroupChat.js @@ -15,7 +15,7 @@ @@ -129,7 +129,7 @@

Source: structures/GroupChat.js

419: 'The participant can\'t be added because the group is full' }; - await window.Store.GroupMetadata.queryAndUpdate(groupWid); + await window.Store.GroupQueryAndUpdate(groupWid); const groupMetadata = group.groupMetadata; const groupParticipants = groupMetadata?.participants; @@ -183,7 +183,7 @@

Source: structures/GroupChat.js

if (autoSendInviteV4 &amp;&amp; rpcResultCode === 403) { let userChat, isInviteV4Sent = false; - window.Store.ContactCollection.gadd(pWid, { silent: true }); + window.Store.Contact.gadd(pWid, { silent: true }); if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' &amp;&amp; (userChat = await window.Store.Chat.find(pWid))) { @@ -312,6 +312,27 @@

Source: structures/GroupChat.js

return true; } + /** + * Updates the group setting to allow only admins to add members to the group. + * @param {boolean} [adminsOnly=true] Enable or disable this option + * @returns {Promise&lt;boolean>} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions. + */ + async setAddMembersAdminsOnly(adminsOnly=true) { + const success = await this.client.pupPage.evaluate(async (groupId, adminsOnly) => { + const chatWid = window.Store.WidFactory.createWid(groupId); + try { + const response = await window.Store.GroupUtils.setGroupMemberAddMode(chatWid, 'member_add_mode', adminsOnly ? 0 : 1); + return response.name === 'SetMemberAddModeResponseSuccess'; + } catch (err) { + if(err.name === 'SmaxParsingFailure') return false; + throw err; + } + }, this.id._serialized, adminsOnly); + + success &amp;&amp; (this.groupMetadata.memberAddMode = adminsOnly ? 'admin_add' : 'all_member_add'); + return success; + } + /** * Updates the group settings to only allow admins to send messages. * @param {boolean} [adminsOnly=true] Enable or disable this option @@ -390,10 +411,18 @@

Source: structures/GroupChat.js

async getInviteCode() { const codeRes = await this.client.pupPage.evaluate(async chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.GroupInvite.queryGroupInviteCode(chatWid); + try { + return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') + ? await window.Store.GroupInvite.queryGroupInviteCode(chatWid, true) + : await window.Store.GroupInvite.queryGroupInviteCode(chatWid); + } + catch (err) { + if(err.name === 'ServerStatusCodeError') return undefined; + throw err; + } }, this.id._serialized); - return codeRes.code; + return codeRes?.code; } /** @@ -485,7 +514,7 @@

Source: structures/GroupChat.js

diff --git a/docs/structures_GroupNotification.js.html b/docs/structures_GroupNotification.js.html index 9e7113921a..2ebf6d216f 100644 --- a/docs/structures_GroupNotification.js.html +++ b/docs/structures_GroupNotification.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/GroupNotification.js + whatsapp-web.js 1.26.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 a0df02ac97..74aee292d7 100644 --- a/docs/structures_Label.js.html +++ b/docs/structures_Label.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Label.js + whatsapp-web.js 1.26.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 a6e5e511ae..722e99fc2a 100644 --- a/docs/structures_List.js.html +++ b/docs/structures_List.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/List.js + whatsapp-web.js 1.26.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 c56cb78d8b..217e826374 100644 --- a/docs/structures_Location.js.html +++ b/docs/structures_Location.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Location.js + whatsapp-web.js 1.26.0 » Source: structures/Location.js @@ -15,7 +15,7 @@ @@ -99,7 +99,7 @@

Source: structures/Location.js

diff --git a/docs/structures_Message.js.html b/docs/structures_Message.js.html index 6b1b4bbc6e..5013f81003 100644 --- a/docs/structures_Message.js.html +++ b/docs/structures_Message.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Message.js + whatsapp-web.js 1.26.0 » Source: structures/Message.js @@ -15,7 +15,7 @@ @@ -37,8 +37,8 @@

Source: structures/Message.js

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

Source: structures/Message.js

inviteCodeExp: data.inviteCodeExp, groupId: data.inviteGrp, groupName: data.inviteGrpName, - fromId: '_serialized' in data.from ? data.from._serialized : data.from, - toId: '_serialized' in data.to ? data.to._serialized : data.to + fromId: typeof data.from === 'object' &amp;&amp; '_serialized' in data.from ? data.from._serialized : data.from, + toId: typeof data.to === 'object' &amp;&amp; '_serialized' in data.to ? data.to._serialized : data.to } : undefined; + /** + * @typedef {Object} Mention + * @property {string} server + * @property {string} user + * @property {string} _serialized + */ + /** * Indicates the mentions in the message body. - * @type {Array&lt;string>} + * @type {Mention[]} */ - this.mentionedIds = []; + this.mentionedIds = data.mentionedJidList || []; - if (data.mentionedJidList) { - this.mentionedIds = data.mentionedJidList; - } + /** + * @typedef {Object} GroupMention + * @property {string} groupSubject The name of the group + * @property {Object} groupJid The group ID + * @property {string} groupJid.server + * @property {string} groupJid.user + * @property {string} groupJid._serialized + */ + + /** + * Indicates whether there are group mentions in the message body + * @type {GroupMention[]} + */ + this.groupMentions = data.groupMentions || []; /** * Order ID for message type ORDER @@ -308,12 +326,7 @@

Source: structures/Message.js

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; + this.messageSecret = Object.keys(data.messageSecret).map((key) => data.messageSecret[key]); } return super._patch(data); @@ -329,9 +342,9 @@

Source: structures/Message.js

* @returns {Promise&lt;Message>} */ async reload() { - const newData = await this.client.pupPage.evaluate((msgId) => { - const msg = window.Store.Msg.get(msgId); - if(!msg) return null; + const newData = await this.client.pupPage.evaluate(async (msgId) => { + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + if (!msg) return null; return window.WWebJS.getMessageModel(msg); }, this.id._serialized); @@ -372,6 +385,14 @@

Source: structures/Message.js

async getMentions() { return await Promise.all(this.mentionedIds.map(async m => await this.client.getContactById(m))); } + + /** + * Returns groups mentioned in this message + * @returns {Promise&lt;GroupChat[]|[]>} + */ + async getGroupMentions() { + return await Promise.all(this.groupMentions.map(async (m) => await this.client.getChatById(m.groupJid._serialized))); + } /** * Returns the quoted message, if any @@ -380,8 +401,8 @@

Source: structures/Message.js

async getQuotedMessage() { if (!this.hasQuotedMsg) return undefined; - const quotedMsg = await this.client.pupPage.evaluate((msgId) => { - const msg = window.Store.Msg.get(msgId); + const quotedMsg = await this.client.pupPage.evaluate(async (msgId) => { + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; const quotedMsg = window.Store.QuotedMsg.getQuotedMsgObj(msg); return window.WWebJS.getMessageModel(quotedMsg); }, this.id._serialized); @@ -419,9 +440,10 @@

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); + if (!messageId) return null; + const msg = + window.Store.Msg.get(messageId) || (await window.Store.Msg.getMessagesById([messageId]))?.messages?.[0]; + if(!msg) return null; await window.Store.sendReactionToMsg(msg, reaction); }, this.id._serialized, reaction); } @@ -444,10 +466,7 @@

Source: structures/Message.js

const chatId = typeof chat === 'string' ? chat : chat.id._serialized; await this.client.pupPage.evaluate(async (msgId, chatId) => { - let msg = window.Store.Msg.get(msgId); - let chat = window.Store.Chat.get(chatId); - - return await chat.forwardMessages([msg]); + return window.WWebJS.forwardMessage(chatId, msgId); }, this.id._serialized, chatId); } @@ -461,9 +480,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; + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + if (!msg || !msg.mediaData) { + return null; } if (msg.mediaData.mediaStage != 'RESOLVED') { // try to resolve media @@ -513,12 +532,16 @@

Source: structures/Message.js

*/ async delete(everyone) { await this.client.pupPage.evaluate(async (msgId, everyone) => { - let msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; 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' }); + if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')) { + return window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: true }); + } else { + return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); + } } return window.Store.Cmd.sendDeleteMsgs(chat, [msg], true); @@ -530,8 +553,7 @@

Source: structures/Message.js

*/ async star() { await this.client.pupPage.evaluate(async (msgId) => { - let msg = window.Store.Msg.get(msgId); - + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (window.Store.MsgActionChecks.canStarMsg(msg)) { let chat = await window.Store.Chat.find(msg.id.remote); return window.Store.Cmd.sendStarMsgs(chat, [msg], false); @@ -544,8 +566,7 @@

Source: structures/Message.js

*/ async unstar() { await this.client.pupPage.evaluate(async (msgId) => { - let msg = window.Store.Msg.get(msgId); - + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (window.Store.MsgActionChecks.canStarMsg(msg)) { let chat = await window.Store.Chat.find(msg.id.remote); return window.Store.Cmd.sendUnstarMsgs(chat, [msg], false); @@ -553,6 +574,27 @@

Source: structures/Message.js

}, this.id._serialized); } + /** + * Pins the message (group admins can pin messages of all group members) + * @param {number} duration The duration in seconds the message will be pinned in a chat + * @returns {Promise&lt;boolean>} Returns true if the operation completed successfully, false otherwise + */ + async pin(duration) { + return await this.client.pupPage.evaluate(async (msgId, duration) => { + return await window.WWebJS.pinUnpinMsgAction(msgId, 1, duration); + }, this.id._serialized, duration); + } + + /** + * Unpins the message (group admins can unpin messages of all group members) + * @returns {Promise&lt;boolean>} Returns true if the operation completed successfully, false otherwise + */ + async unpin() { + return await this.client.pupPage.evaluate(async (msgId) => { + return await window.WWebJS.pinUnpinMsgAction(msgId, 2); + }, this.id._serialized); + } + /** * Message Info * @typedef {Object} MessageInfo @@ -565,15 +607,20 @@

Source: structures/Message.js

*/ /** - * Get information about message delivery status. May return null if the message does not exist or is not sent by you. + * Get information about message delivery status. + * May return null if the message does not exist or is not sent by you. * @returns {Promise&lt;?MessageInfo>} */ async getInfo() { const info = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId); - if (!msg) return null; + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + if (!msg || !msg.id.fromMe) return null; - return await window.Store.MessageInfo.sendQueryMsgInfo(msg.id); + return new Promise((resolve) => { + setTimeout(async () => { + resolve(await window.Store.getMsgInfo(msg.id)); + }, (Date.now() - msg.t * 1000 &lt; 1250) &amp;&amp; Math.floor(Math.random() * (1200 - 1100 + 1)) + 1100 || 0); + }); }, this.id._serialized); return info; @@ -600,7 +647,7 @@

Source: structures/Message.js

async getPayment() { if (this.type === MessageTypes.PAYMENT) { const msg = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if(!msg) return null; return msg.serialize(); }, this.id._serialized); @@ -654,12 +701,20 @@

Source: structures/Message.js

* @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); + if (options.mentions) { + !Array.isArray(options.mentions) &amp;&amp; (options.mentions = [options.mentions]); + if (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); + } } + + options.groupMentions &amp;&amp; !Array.isArray(options.groupMentions) &amp;&amp; (options.groupMentions = [options.groupMentions]); + let internalOptions = { linkPreview: options.linkPreview === false ? undefined : true, - mentionedJidList: Array.isArray(options.mentions) ? options.mentions : [], + mentionedJidList: options.mentions || [], + groupMentions: options.groupMentions, extraOptions: options.extra }; @@ -667,11 +722,11 @@

Source: structures/Message.js

return null; } const messageEdit = await this.client.pupPage.evaluate(async (msgId, message, options) => { - let msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (!msg) return null; - let catEdit = (msg.type === 'chat' &amp;&amp; window.Store.MsgActionChecks.canEditText(msg)); - if (catEdit) { + let canEdit = window.Store.MsgActionChecks.canEditText(msg) || window.Store.MsgActionChecks.canEditCaption(msg); + if (canEdit) { const msgEdit = await window.WWebJS.editMessage(msg, message, options); return msgEdit.serialize(); } @@ -695,7 +750,7 @@

Source: structures/Message.js

diff --git a/docs/structures_MessageMedia.js.html b/docs/structures_MessageMedia.js.html index 60a12484d0..4b334eb6ae 100644 --- a/docs/structures_MessageMedia.js.html +++ b/docs/structures_MessageMedia.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/MessageMedia.js + whatsapp-web.js 1.26.0 » Source: structures/MessageMedia.js @@ -15,7 +15,7 @@ @@ -150,7 +150,7 @@

Source: structures/MessageMedia.js

diff --git a/docs/structures_Order.js.html b/docs/structures_Order.js.html index cb091f3992..4a89915fa9 100644 --- a/docs/structures_Order.js.html +++ b/docs/structures_Order.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Order.js + whatsapp-web.js 1.26.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 f4598b1b8b..a0cd91e08f 100644 --- a/docs/structures_Payment.js.html +++ b/docs/structures_Payment.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Payment.js + whatsapp-web.js 1.26.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 index da5c481702..7e05c31726 100644 --- a/docs/structures_Poll.js.html +++ b/docs/structures_Poll.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Poll.js + whatsapp-web.js 1.26.0 » Source: structures/Poll.js @@ -15,7 +15,7 @@ @@ -83,7 +83,7 @@

Source: structures/Poll.js

diff --git a/docs/structures_PollVote.js.html b/docs/structures_PollVote.js.html new file mode 100644 index 0000000000..63e4c20924 --- /dev/null +++ b/docs/structures_PollVote.js.html @@ -0,0 +1,115 @@ + + + + + + + whatsapp-web.js 1.26.0 » Source: structures/PollVote.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+const Message = require('./Message');
+const Base = require('./Base');
+
+/**
+ * Selected poll option structure
+ * @typedef {Object} SelectedPollOption
+ * @property {number} id The local selected or deselected option ID
+ * @property {string} name The option name
+ */
+
+/**
+ * Represents a Poll Vote on WhatsApp
+ * @extends {Base}
+ */
+class PollVote extends Base {
+    constructor(client, data) {
+        super(client);
+
+        if (data) this._patch(data);
+    }
+
+    _patch(data) {
+        /**
+         * The person who voted
+         * @type {string}
+         */
+        this.voter = data.sender;
+
+        /**
+         * The selected poll option(s)
+         * If it's an empty array, the user hasn't selected any options on the poll,
+         * may occur when they deselected all poll options
+         * @type {SelectedPollOption[]}
+         */
+        this.selectedOptions =
+            data.selectedOptionLocalIds.length > 0
+                ? data.selectedOptionLocalIds.map((e) => ({
+                    name: data.parentMessage.pollOptions.find((x) => x.localId === e).name,
+                    localId: e
+                }))
+                : [];
+
+        /**
+         * Timestamp the option was selected or deselected at
+         * @type {number}
+         */
+        this.interractedAtTs = data.senderTimestampMs;
+
+        /**
+         * The poll creation message associated with the poll vote
+         * @type {Message}
+         */
+        this.parentMessage = new Message(this.client, data.parentMessage);
+
+        return super._patch(data);
+    }
+}
+
+module.exports = PollVote;
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/structures_PrivateChat.js.html b/docs/structures_PrivateChat.js.html index e337bd5958..e8889b9246 100644 --- a/docs/structures_PrivateChat.js.html +++ b/docs/structures_PrivateChat.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/PrivateChat.js + whatsapp-web.js 1.26.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 5017b0f4c7..2df6faedde 100644 --- a/docs/structures_PrivateContact.js.html +++ b/docs/structures_PrivateContact.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/PrivateContact.js + whatsapp-web.js 1.26.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 a5e898f06a..ab12f12518 100644 --- a/docs/structures_Product.js.html +++ b/docs/structures_Product.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Product.js + whatsapp-web.js 1.26.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 c0c38ad6a1..7b09d69981 100644 --- a/docs/structures_ProductMetadata.js.html +++ b/docs/structures_ProductMetadata.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/ProductMetadata.js + whatsapp-web.js 1.26.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 index a2ac667564..80ff22a5b0 100644 --- a/docs/structures_Reaction.js.html +++ b/docs/structures_Reaction.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: structures/Reaction.js + whatsapp-web.js 1.26.0 » Source: structures/Reaction.js @@ -15,7 +15,7 @@ @@ -107,7 +107,7 @@

Source: structures/Reaction.js

diff --git a/docs/util_Constants.js.html b/docs/util_Constants.js.html index 596f819f5e..c045d043b2 100644 --- a/docs/util_Constants.js.html +++ b/docs/util_Constants.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: util/Constants.js + whatsapp-web.js 1.26.0 » Source: util/Constants.js @@ -15,7 +15,7 @@ @@ -75,6 +75,7 @@

Source: util/Constants.js

CHAT_REMOVED: 'chat_removed', CHAT_ARCHIVED: 'chat_archived', MESSAGE_RECEIVED: 'message', + MESSAGE_CIPHERTEXT: 'message_ciphertext', MESSAGE_CREATE: 'message_create', MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone', MESSAGE_REVOKED_ME: 'message_revoke_me', @@ -95,7 +96,8 @@

Source: util/Constants.js

STATE_CHANGED: 'change_state', BATTERY_CHANGED: 'change_battery', INCOMING_CALL: 'call', - REMOTE_SESSION_SAVED: 'remote_session_saved' + REMOTE_SESSION_SAVED: 'remote_session_saved', + VOTE_UPDATE: 'vote_update' }; /** @@ -213,7 +215,7 @@

Source: util/Constants.js

diff --git a/docs/util_Injected_LegacyStore.js.html b/docs/util_Injected_LegacyStore.js.html new file mode 100644 index 0000000000..82d7127688 --- /dev/null +++ b/docs/util_Injected_LegacyStore.js.html @@ -0,0 +1,199 @@ + + + + + + + whatsapp-web.js 1.26.0 » Source: util/Injected/LegacyStore.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+//TODO: To be removed by version 2.3000.x hard release
+
+// Exposes the internal Store to the WhatsApp Web client
+exports.ExposeLegacyStore = () => {
+    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.GroupQueryAndUpdate = window.mR.findModule('queryAndUpdateGroupMetadataById')[0].queryAndUpdateGroupMetadataById;
+    window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection;
+    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.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.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.LinkPreview = window.mR.findModule('getLinkPreview')[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];
+    window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo;
+    window.Store.pinUnpinMsg = window.mR.findModule('sendPinInChatMsg')[0].sendPinInChatMsg;
+    
+    /* 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.Settings = {
+        ...window.mR.findModule('ChatlistPanelState')[0],
+        setPushname: window.mR.findModule((m) => m.setPushname &amp;&amp; !m.ChatlistPanelState)[0].setPushname
+    };
+    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;
+
+    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} module The name or a key of the target module to search
+     * @property {number} index The index value of the target module
+     * @property {string} function 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.module === 'string'
+            ? window.mR.findModule(target.module)
+            : window.mR.modules[target.module];
+        const originalFunction = module[target.index][target.function];
+        const modifiedFunction = (...args) => callback(originalFunction, ...args);
+        module[target.index][target.function] = modifiedFunction;
+    };
+
+    window.injectToFunction({ module: 'mediaTypeFromProtobuf', index: 0, function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });
+
+    window.injectToFunction({ module: 'typeAttributeFromProtobuf', index: 0, function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });
+};
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/util_Injected_Store.js.html b/docs/util_Injected_Store.js.html new file mode 100644 index 0000000000..b96ac29fb9 --- /dev/null +++ b/docs/util_Injected_Store.js.html @@ -0,0 +1,221 @@ + + + + + + + whatsapp-web.js 1.26.0 » Source: util/Injected/Store.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+exports.ExposeStore = () => {
+    /**
+     * 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 (!['>', '>=', '&lt;', '&lt;=', '='].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 === '&lt;' ? lOperand &lt; rOperand :
+                        operator === '&lt;=' ? lOperand &lt;= rOperand :
+                            operator === '=' ? lOperand === rOperand :
+                                false
+        );
+    };
+
+    window.Store = Object.assign({}, window.require('WAWebCollections'));
+    window.Store.AppState = window.require('WAWebSocketModel').Socket;
+    window.Store.BlockContact = window.require('WAWebBlockContactAction');
+    window.Store.Conn = window.require('WAWebConnModel').Conn;
+    window.Store.Cmd = window.require('WAWebCmd').Cmd;
+    window.Store.DownloadManager = window.require('WAWebDownloadManager').downloadManager;
+    window.Store.GroupQueryAndUpdate = window.require('WAWebGroupQueryJob').queryAndUpdateGroupMetadataById;
+    window.Store.MediaPrep = window.require('WAWebPrepRawMedia');
+    window.Store.MediaObject = window.require('WAWebMediaStorage');
+    window.Store.MediaTypes = window.require('WAWebMmsMediaTypes');
+    window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload');
+    window.Store.MsgKey = window.require('WAWebMsgKey');
+    window.Store.NumberInfo = window.require('WAPhoneUtils');
+    window.Store.OpaqueData = window.require('WAWebMediaOpaqueData');
+    window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge');
+    window.Store.QueryOrder = window.require('WAWebBizOrderBridge');
+    window.Store.SendClear = window.require('WAWebChatClearBridge');
+    window.Store.SendDelete = window.require('WAWebDeleteChatAction');
+    window.Store.SendMessage = window.require('WAWebSendMsgChatAction');
+    window.Store.EditMessage = window.require('WAWebSendMessageEditAction');
+    window.Store.SendSeen = window.require('WAWebUpdateUnreadChatAction');
+    window.Store.User = window.require('WAWebUserPrefsMeUser');
+    window.Store.ContactMethods = window.require('WAWebContactGetters');
+    window.Store.UploadUtils = window.require('WAWebUploadManager');
+    window.Store.UserConstructor = window.require('WAWebWid');
+    window.Store.Validators = window.require('WALinkify');
+    window.Store.VCard = window.require('WAWebFrontendVcardUtils');
+    window.Store.WidFactory = window.require('WAWebWidFactory');
+    window.Store.ProfilePic = window.require('WAWebContactProfilePicThumbBridge');
+    window.Store.PresenceUtils = window.require('WAWebPresenceChatAction');
+    window.Store.ChatState = window.require('WAWebChatStateBridge');
+    window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups;
+    window.Store.StatusUtils = window.require('WAWebContactStatusBridge');
+    window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages');
+    window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg;
+    window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions');
+    window.Store.EphemeralFields = window.require('WAWebGetEphemeralFieldsMsgActionsUtils');
+    window.Store.MsgActionChecks = window.require('WAWebMsgActionCapability');
+    window.Store.QuotedMsg = window.require('WAWebQuotedMsgModelUtils');
+    window.Store.LinkPreview = window.require('WAWebLinkPreviewChatAction');
+    window.Store.Socket = window.require('WADeprecatedSendIq');
+    window.Store.SocketWap = window.require('WAWap');
+    window.Store.SearchContext = window.require('WAWebChatMessageSearch').getSearchContext;
+    window.Store.DrawerManager = window.require('WAWebDrawerManager').DrawerManager;
+    window.Store.LidUtils = window.require('WAWebApiContact');
+    window.Store.WidToJid = window.require('WAWebWidToJid');
+    window.Store.JidToWid = window.require('WAWebJidToWid');
+    window.Store.getMsgInfo = window.require('WAWebApiMessageInfoStore').queryMsgInfo;
+    window.Store.pinUnpinMsg = window.require('WAWebSendPinMessageAction').sendPinInChatMsg;
+    window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists;
+    window.Store.ReplyUtils = window.require('WAWebMsgReply');
+    window.Store.Settings = window.require('WAWebUserPrefsGeneral');
+    window.Store.BotSecret = window.require('WAWebBotMessageSecret');
+    window.Store.BotProfiles = window.require('WAWebBotProfileCollection');
+    window.Store.DeviceList = window.require('WAWebApiDeviceList');
+    if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) 
+        window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
+    
+    window.Store.ForwardUtils = {
+        ...window.require('WAWebForwardMessagesToChat')
+    };
+
+    window.Store.StickerTools = {
+        ...window.require('WAWebImageUtils'),
+        ...window.require('WAWebAddWebpMetadata')
+    };
+    window.Store.GroupUtils = {
+        ...window.require('WAWebGroupCreateJob'),
+        ...window.require('WAWebGroupModifyInfoJob'),
+        ...window.require('WAWebExitGroupAction'),
+        ...window.require('WAWebContactProfilePicThumbBridge')
+    };
+    window.Store.GroupParticipants = {
+        ...window.require('WAWebModifyParticipantsGroupAction'),
+        ...window.require('WASmaxGroupsAddParticipantsRPC')
+    };
+    window.Store.GroupInvite = {
+        ...window.require('WAWebGroupInviteJob'),
+        ...window.require('WAWebGroupQueryJob')
+    };
+    window.Store.GroupInviteV4 = {
+        ...window.require('WAWebGroupInviteV4Job'),
+        ...window.require('WAWebChatSendMessages')
+    };
+    window.Store.MembershipRequestUtils = {
+        ...window.require('WAWebApiMembershipApprovalRequestStore'),
+        ...window.require('WASmaxGroupsMembershipRequestsActionRPC')
+    };
+
+    if (!window.Store.Chat._find || !window.Store.Chat.findImpl) {
+        window.Store.Chat._find = e => {
+            const target = window.Store.Chat.get(e);
+            return target ? Promise.resolve(target) : Promise.resolve({
+                id: e
+            });
+        };
+        window.Store.Chat.findImpl = window.Store.Chat._find;
+    }
+
+    /**
+     * Target options object description
+     * @typedef {Object} TargetOptions
+     * @property {string|number} module The target module
+     * @property {string} function 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 = window.require(target.module);
+        const originalFunction = module[target.function];
+        const modifiedFunction = (...args) => callback(originalFunction, ...args);
+        module[target.function] = modifiedFunction;
+    };
+
+    window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });
+
+    window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });
+
+};
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/util_Injected_Utils.js.html b/docs/util_Injected_Utils.js.html new file mode 100644 index 0000000000..2cf922c694 --- /dev/null +++ b/docs/util_Injected_Utils.js.html @@ -0,0 +1,1071 @@ + + + + + + + whatsapp-web.js 1.26.0 » Source: util/Injected/Utils.js + + + + + + + + +
+
+
+ +
+ +
+
'use strict';
+
+exports.LoadUtils = () => {
+    window.WWebJS = {};
+
+    window.WWebJS.forwardMessage = async (chatId, msgId) => {
+        const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
+        let chat = window.Store.Chat.get(chatId);
+
+        if (window.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) {
+            return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], false);
+        } else {
+            return chat.forwardMessages([msg]);
+        }
+    };
+
+    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
+                });
+            
+            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 = await window.Store.Msg.getMessagesById([options.quotedMessageId]);
+
+            if (quotedMessage['messages'].length != 1) {
+                throw new Error('Could not get the quoted message.');
+            }
+
+            quotedMessage = quotedMessage['messages'][0];
+
+            // 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 = await Promise.all(
+                options.mentionedJidList.map(async (id) => {
+                    const wid = window.Store.WidFactory.createWid(id);
+                    if (await window.Store.QueryExist(wid)) {
+                        return wid;
+                    }
+                })
+            );
+            options.mentionedJidList = options.mentionedJidList.filter(Boolean);
+        }
+
+        if (options.groupMentions) {
+            options.groupMentions = options.groupMentions.map((e) => ({
+                groupSubject: e.subject,
+                groupJid: window.Store.WidFactory.createWid(e.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;
+            const link = window.Store.Validators.findLink(content);
+            if (link) {
+                let preview = await window.Store.LinkPreview.getLinkPreview(link);
+                if (preview &amp;&amp; preview.data) {
+                    preview = preview.data;
+                    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 botOptions = {};
+        if (options.invokedBotWid) {
+            botOptions.messageSecret = window.crypto.getRandomValues(new Uint8Array(32));
+            botOptions.botMessageSecret = await window.Store.BotSecret.genBotMsgSecretFromMsgSecret(botOptions.messageSecret);
+            botOptions.invokedBotWid = window.Store.WidFactory.createWid(options.invokedBotWid);
+            botOptions.botPersonaId = window.Store.BotProfiles.BotProfileCollection.get(options.invokedBotWid).personaId;
+            delete options.invokedBotWid;
+        }
+
+        const meUser = window.Store.User.getMaybeMeUser();
+        const newId = await window.Store.MsgKey.newId();
+        
+        const newMsgId = new window.Store.MsgKey({
+            from: meUser,
+            to: chat.id,
+            id: newId,
+            participant: 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,
+            ...botOptions,
+            ...extraOptions
+        };
+        
+        // Bot's won't reply if canonicalUrl is set (linking)
+        if (botOptions) {
+            delete message.canonicalUrl;
+        }
+
+        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 = await Promise.all(
+                options.mentionedJidList.map(async (id) => {
+                    const wid = window.Store.WidFactory.createWid(id);
+                    if (await window.Store.QueryExist(wid)) {
+                        return wid;
+                    }
+                })
+            );
+            options.mentionedJidList = options.mentionedJidList.filter(Boolean);
+        }
+
+        if (options.groupMentions) {
+            options.groupMentions = options.groupMentions.map((e) => ({
+                groupSubject: e.subject,
+                groupJid: window.Store.WidFactory.createWid(e.id)
+            }));
+        }
+
+        if (options.linkPreview) {
+            delete options.linkPreview;
+            const link = window.Store.Validators.findLink(content);
+            if (link) {
+                const preview = await window.Store.LinkPreview.getLinkPreview(link);
+                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 = (window.Store.Validators.findLinks(message.mediaObject ? message.caption : message.body)).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.getPollVoteModel = async (vote) => {
+        const _vote = vote.serialize();
+        if (!vote.parentMsgKey) return null;
+        const msg =
+            window.Store.Msg.get(vote.parentMsgKey) || (await window.Store.Msg.getMessagesById([vote.parentMsgKey]))?.messages?.[0];
+        msg &amp;&amp; (_vote.parentMessage = window.WWebJS.getMessageModel(msg));
+        return _vote;
+    };
+
+    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) || (await window.Store.Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0]
+                : 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.BusinessProfile.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) => {
+        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.GroupQueryAndUpdate(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 [];
+        }
+    };
+
+    window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => {
+        const message = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
+        if (!message) return false;
+        const response = await window.Store.pinUnpinMsg(message, action, duration);
+        return response.messageSendResult === 'OK';
+    };
+
+};
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/docs/util_InterfaceController.js.html b/docs/util_InterfaceController.js.html index 9c0196c82a..ec6a5c2aaf 100644 --- a/docs/util_InterfaceController.js.html +++ b/docs/util_InterfaceController.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: util/InterfaceController.js + whatsapp-web.js 1.26.0 » Source: util/InterfaceController.js @@ -15,7 +15,7 @@ @@ -80,7 +80,7 @@

Source: util/InterfaceController.js

*/ async openChatWindowAt(msgId) { await this.pupPage.evaluate(async msgId => { - let msg = await window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; 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); @@ -93,7 +93,7 @@

Source: util/InterfaceController.js

*/ async openMessageDrawer(msgId) { await this.pupPage.evaluate(async msgId => { - let msg = await window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; await window.Store.Cmd.msgInfoDrawer(msg); }, msgId); } @@ -166,7 +166,7 @@

Source: util/InterfaceController.js

diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index d8ce40d09d..bda87a945c 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: util/Util.js + whatsapp-web.js 1.26.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 index ccdb09d839..d820adf976 100644 --- a/docs/webCache_LocalWebCache.js.html +++ b/docs/webCache_LocalWebCache.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: webCache/LocalWebCache.js + whatsapp-web.js 1.26.0 » Source: webCache/LocalWebCache.js @@ -15,7 +15,7 @@ @@ -60,18 +60,16 @@

Source: webCache/LocalWebCache.js

} } - 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; - + async persist(indexHtml, version) { + // version = (version+'').replace(/[^0-9.]/g,''); const filePath = path.join(this.path, `${version}.html`); fs.mkdirSync(this.path, { recursive: true }); fs.writeFileSync(filePath, indexHtml); } } -module.exports = LocalWebCache; +module.exports = LocalWebCache; +
@@ -81,7 +79,7 @@

Source: webCache/LocalWebCache.js

diff --git a/docs/webCache_RemoteWebCache.js.html b/docs/webCache_RemoteWebCache.js.html index 75a643ed65..88d4f7b896 100644 --- a/docs/webCache_RemoteWebCache.js.html +++ b/docs/webCache_RemoteWebCache.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: webCache/RemoteWebCache.js + whatsapp-web.js 1.26.0 » Source: webCache/RemoteWebCache.js @@ -15,7 +15,7 @@ @@ -78,7 +78,7 @@

Source: webCache/RemoteWebCache.js

diff --git a/docs/webCache_WebCache.js.html b/docs/webCache_WebCache.js.html index a19a40e10c..7f335795bd 100644 --- a/docs/webCache_WebCache.js.html +++ b/docs/webCache_WebCache.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.23.0 » Source: webCache/WebCache.js + whatsapp-web.js 1.26.0 » Source: webCache/WebCache.js @@ -15,7 +15,7 @@ @@ -52,7 +52,7 @@

Source: webCache/WebCache.js

diff --git a/example.js b/example.js index a3ade0e0aa..3334e5b6a2 100644 --- a/example.js +++ b/example.js @@ -5,19 +5,30 @@ const client = new Client({ // proxyAuthentication: { username: 'username', password: 'password' }, puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], - headless: false + headless: false, } }); +// client initialize does not finish at ready now. client.initialize(); client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); -client.on('qr', (qr) => { +// Pairing code only needs to be requested once +let pairingCodeRequested = false; +client.on('qr', async (qr) => { // NOTE: This event will not be fired if a session is specified. console.log('QR RECEIVED', qr); + + // paiuting code example + const pairingCodeEnabled = false; + if (pairingCodeEnabled && !pairingCodeRequested) { + const pairingCode = await client.requestPairingCode('96170100100'); // enter the target phone number + console.log('Pairing code enabled, code: '+ pairingCode); + pairingCodeRequested = true; + } }); client.on('authenticated', () => { @@ -29,8 +40,18 @@ client.on('auth_failure', msg => { console.error('AUTHENTICATION FAILURE', msg); }); -client.on('ready', () => { +client.on('ready', async () => { console.log('READY'); + const debugWWebVersion = await client.getWWebVersion(); + console.log(`WWebVersion = ${debugWWebVersion}`); + + client.pupPage.on('pageerror', function(err) { + console.log('Page error: ' + err.toString()); + }); + client.pupPage.on('error', function(err) { + console.log('Page error: ' + err.toString()); + }); + }); client.on('message', async msg => { @@ -418,7 +439,7 @@ client.on('message', async msg => { requesterIds: ['number1@c.us', 'number2@c.us'], sleep: null }); - } else { + } 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: @@ -429,6 +450,26 @@ client.on('message', async msg => { */ const result = await msg.pin(60); // Will pin a message for 1 minute console.log(result); // True if the operation completed successfully, false otherwise + } else if (msg.body === '!howManyConnections') { + /** + * Get user device count by ID + * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one + * So for a non-enterprise user with one WaWeb connection it should return "2" + */ + let deviceCount = await client.getContactDeviceCount(msg.from); + await msg.reply(`You have *${deviceCount}* devices connected`); + } else if (msg.body === '!syncHistory') { + const isSynced = await client.syncHistory(msg.from); + // Or through the Chat object: + // const chat = await client.getChatById(msg.from); + // const isSynced = await chat.syncHistory(); + + await msg.reply(isSynced ? 'Historical chat is syncing..' : 'There is no historical chat to sync.'); + } else if (msg.body === '!statuses') { + const statuses = await client.getBroadcasts(); + console.log(statuses); + const chat = await statuses[0]?.getChat(); // Get user chat of a first status + console.log(chat); } }); @@ -595,6 +636,10 @@ client.on('group_membership_request', async (notification) => { await client.rejectGroupMembershipRequests(notification.chatId, notification.author); }); +client.on('message_reaction', async (reaction) => { + console.log('REACTION RECEIVED', reaction); +}); + client.on('vote_update', (vote) => { /** The vote that was affected: */ console.log(vote); diff --git a/index.d.ts b/index.d.ts index e1bf8e0714..7b90dec421 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'events' import { RequestInit } from 'node-fetch' import * as puppeteer from 'puppeteer' +import InterfaceController from './src/util/InterfaceController' declare namespace WAWebJS { @@ -17,6 +18,9 @@ declare namespace WAWebJS { /** Puppeteer browser running WhatsApp Web */ pupBrowser?: puppeteer.Browser + /** Client interactivity interface */ + interface?: InterfaceController + /**Accepts an invitation to join a group */ acceptInvite(inviteCode: string): Promise @@ -74,6 +78,9 @@ declare namespace WAWebJS { /** Get all current Labels */ getLabels(): Promise + /** Get all current Broadcasts */ + getBroadcasts(): Promise + /** Change labels in chats */ addOrRemoveLabels(labelIds: Array, chatIds: Array): Promise @@ -114,6 +121,14 @@ declare namespace WAWebJS { */ muteChat(chatId: string, unmuteDate?: Date): Promise + /** + * Request authentication via pairing code instead of QR code + * @param phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) + * @param showNotification - Show notification to pair on phone number + * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" + */ + requestPairingCode(phoneNumber: string, showNotification = true): Promise + /** Force reset of connection state for the client */ resetState(): Promise @@ -167,7 +182,18 @@ declare namespace WAWebJS { * @param flag true/false on or off */ setAutoDownloadVideos(flag: boolean): Promise - + + /** + * Get user device count by ID + * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one + * So for a non-enterprise user with one WaWeb connection it should return "2" + * @param {string} contactId + */ + getContactDeviceCount(userId: string): Promise + + /** Sync history conversation of the Chat */ + syncHistory(chatId: string): Promise + /** Changes and returns the archive state of the Chat */ unarchiveChat(chatId: string): Promise @@ -218,7 +244,7 @@ declare namespace WAWebJS { /** Emitted when the client has been disconnected */ on(event: 'disconnected', listener: ( /** reason that caused the disconnect */ - reason: WAState | "NAVIGATION" + reason: WAState | "LOGOUT" ) => void): this /** Emitted when a user joins the chat via invite link or is added by an admin */ @@ -1069,6 +1095,28 @@ declare namespace WAWebJS { getChats: () => Promise } + export interface Broadcast { + /** Chat Object ID */ + id: { + server: string, + user: string, + _serialized: string + }, + /** Unix timestamp of last story */ + timestamp: number, + /** Number of available statuses */ + totalCount: number, + /** Number of not viewed */ + unreadCount: number, + /** Unix timestamp of last story */ + msgs: Message[], + + /** Returns the Chat of the owner of the story */ + getChat: () => Promise, + /** Returns the Contact of the owner of the story */ + getContact: () => Promise, + } + /** Options for sending a message */ export interface MessageSendOptions { /** Show links preview. Has no effect on multi-device accounts. */ @@ -1100,6 +1148,8 @@ declare namespace WAWebJS { }[] /** Send 'seen' status */ sendSeen?: boolean + /** Bot Wid when doing a bot mention like @Meta AI */ + invokedBotWid?: string /** Media to be sent */ media?: MessageMedia /** Extra options */ @@ -1423,6 +1473,8 @@ declare namespace WAWebJS { changeLabels: (labelIds: Array) => Promise /** Gets instances of all pinned messages in a chat */ getPinnedMessages: () => Promise<[Message]|[]> + /** Sync history conversation of the Chat */ + syncHistory: () => Promise } export interface MessageSearchOptions { diff --git a/index.js b/index.js index 9b7bf85bac..aebe6ce08c 100644 --- a/index.js +++ b/index.js @@ -22,12 +22,12 @@ module.exports = { ProductMetadata: require('./src/structures/ProductMetadata'), List: require('./src/structures/List'), Buttons: require('./src/structures/Buttons'), + Broadcast: require('./src/structures/Broadcast'), // Auth Strategies NoAuth: require('./src/authStrategies/NoAuth'), LocalAuth: require('./src/authStrategies/LocalAuth'), RemoteAuth: require('./src/authStrategies/RemoteAuth'), - LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'), ...Constants }; diff --git a/package.json b/package.json index efa675bcd7..e7d31db38f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "whatsapp-web.js", - "version": "1.24.0", + "version": "1.26.1-alpha.1", "description": "Library for interacting with the WhatsApp Web API ", "main": "./index.js", "typings": "./index.d.ts", diff --git a/src/Client.js b/src/Client.js index 735b31449c..f4805f2220 100644 --- a/src/Client.js +++ b/src/Client.js @@ -7,13 +7,17 @@ const moduleRaid = require('@pedroslopez/moduleraid/moduleraid'); const Util = require('./util/Util'); const InterfaceController = require('./util/InterfaceController'); const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants'); -const { ExposeStore, LoadUtils } = require('./util/Injected'); +const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore'); +const { ExposeStore } = require('./util/Injected/Store'); +const { ExposeLegacyAuthStore } = require('./util/Injected/AuthStore/LegacyAuthStore'); +const { ExposeLegacyStore } = require('./util/Injected/LegacyStore'); +const { LoadUtils } = require('./util/Injected/Utils'); const ChatFactory = require('./factories/ChatFactory'); const ContactFactory = require('./factories/ContactFactory'); const WebCacheFactory = require('./webCache/WebCacheFactory'); -const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures'); -const LegacySessionAuth = require('./authStrategies/LegacySessionAuth'); +const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction, Broadcast} = require('./structures'); const NoAuth = require('./authStrategies/NoAuth'); +const {exposeFunctionIfAbsent} = require('./util/Puppeteer'); /** * Starting point for interacting with the WhatsApp Web API @@ -63,156 +67,64 @@ class Client extends EventEmitter { this.options = Util.mergeDefault(DefaultOptions, options); if(!this.options.authStrategy) { - if(Object.prototype.hasOwnProperty.call(this.options, 'session')) { - process.emitWarning( - 'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' + - 'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).', - 'DeprecationWarning' - ); - - this.authStrategy = new LegacySessionAuth({ - session: this.options.session, - restartOnAuthFail: this.options.restartOnAuthFail - }); - } else { - this.authStrategy = new NoAuth(); - } + this.authStrategy = new NoAuth(); } else { this.authStrategy = this.options.authStrategy; } this.authStrategy.setup(this); + /** + * @type {puppeteer.Browser} + */ this.pupBrowser = null; + /** + * @type {puppeteer.Page} + */ this.pupPage = null; + this.currentIndexHtml = null; + this.lastLoggedOut = false; + Util.setFfmpegPath(this.options.ffmpegPath); } - /** - * Sets up events and requirements, kicks off authentication request + * Injection logic + * Private function */ - async initialize() { - let [browser, page] = [null, null]; + async inject() { + await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs}); - await this.authStrategy.beforeBrowserInitialized(); + const version = await this.getWWebVersion(); + const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000; - const puppeteerOpts = this.options.puppeteer; - if (puppeteerOpts && puppeteerOpts.browserWSEndpoint) { - browser = await puppeteer.connect(puppeteerOpts); - page = await browser.newPage(); + if (isCometOrAbove) { + await this.pupPage.evaluate(ExposeAuthStore); } else { - const browserArgs = [...(puppeteerOpts.args || [])]; - if(!browserArgs.find(arg => arg.includes('--user-agent'))) { - browserArgs.push(`--user-agent=${this.options.userAgent}`); - } - // navigator.webdriver fix - browserArgs.push('--disable-blink-features=AutomationControlled'); - - browser = await puppeteer.launch({...puppeteerOpts, args: browserArgs}); - page = (await browser.pages())[0]; - } - - if (this.options.proxyAuthentication !== undefined) { - await page.authenticate(this.options.proxyAuthentication); + await this.pupPage.evaluate(ExposeLegacyAuthStore, moduleRaid.toString()); } - - await page.setUserAgent(this.options.userAgent); - if (this.options.bypassCSP) await page.setBypassCSP(true); - this.pupBrowser = browser; - this.pupPage = page; - - await this.authStrategy.afterBrowserInitialized(); - await this.initWebVersionCache(); - - // ocVesion (isOfficialClient patch) - await page.evaluateOnNewDocument(() => { - const originalError = Error; - //eslint-disable-next-line no-global-assign - Error = function (message) { - const error = new originalError(message); - const originalStack = error.stack; - if (error.stack.includes('moduleRaid')) error.stack = originalStack + '\n at https://web.whatsapp.com/vendors~lazy_loaded_low_priority_components.05e98054dbd60f980427.js:2:44'; - return error; - }; - }); - - await page.goto(WhatsWebURL, { - waitUntil: 'load', - timeout: 0, - referer: 'https://whatsapp.com/' - }); - - 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; + const needAuthentication = await this.pupPage.evaluate(async () => { + let state = window.AuthStore.AppState.state; + + if (state === 'OPENING' || state === 'UNLAUNCHED' || state === 'PAIRING') { + // wait till state changes + await new Promise(r => { + window.AuthStore.AppState.on('change:state', function waitTillInit(_AppState, state) { + if (state !== 'OPENING' && state !== 'UNLAUNCHED' && state !== 'PAIRING') { + window.AuthStore.AppState.off('change:state', waitTillInit); + r(); + } + }); + }); } + state = window.AuthStore.AppState.state; + return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE'; }); - 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 - const needAuthentication = await Promise.race([ - new Promise(resolve => { - page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs }) - .then(() => resolve(false)) - .catch((err) => resolve(err)); - }), - new Promise(resolve => { - page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs }) - .then(() => resolve(true)) - .catch((err) => resolve(err)); - }) - ]); - - // Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race; - if (needAuthentication instanceof Error) throw needAuthentication; - - // Scan-qrcode selector was found. Needs authentication if (needAuthentication) { const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); + if(failed) { /** * Emitted when there has been an error while trying to restore an existing session @@ -228,10 +140,9 @@ class Client extends EventEmitter { return; } - const QR_CONTAINER = 'div[data-ref]'; - const QR_RETRY_BUTTON = 'div[data-ref] > span > button'; + // Register qr events let qrRetries = 0; - await page.exposeFunction('qrChanged', async (qr) => { + await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => { /** * Emitted when a QR code is received * @event Client#qr @@ -247,173 +158,249 @@ class Client extends EventEmitter { } }); - await page.evaluate(function (selectors) { - const qr_container = document.querySelector(selectors.QR_CONTAINER); - window.qrChanged(qr_container.dataset.ref); - - const obs = new MutationObserver((muts) => { - muts.forEach(mut => { - // Listens to qr token change - if (mut.type === 'attributes' && mut.attributeName === 'data-ref') { - window.qrChanged(mut.target.dataset.ref); - } - // Listens to retry button, when found, click it - else if (mut.type === 'childList') { - const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON); - if (retry_button) retry_button.click(); - } - }); - }); - obs.observe(qr_container.parentElement, { - subtree: true, - childList: true, - attributes: true, - attributeFilter: ['data-ref'], - }); - }, { - QR_CONTAINER, - QR_RETRY_BUTTON - }); - // Wait for code scan - try { - await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 }); - } catch(error) { - if ( - error.name === 'ProtocolError' && - error.message && - error.message.match(/Target closed/) - ) { - // something has called .destroy() while waiting - return; - } + await this.pupPage.evaluate(async () => { + const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); + const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get(); + const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey); + const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey); + const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey(); + const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; + const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform; + + window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr + window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes + }); + } - throw error; + await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => { + if (state == 'UNPAIRED_IDLE') { + // refresh qr code + window.Store.Cmd.refreshQR(); } + }); - } - - await page.evaluate(() => { + await exposeFunctionIfAbsent(this.pupPage, 'onAppStateHasSyncedEvent', async () => { + const authEventPayload = await this.authStrategy.getAuthEventPayload(); /** - * 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'); + * Emitted when authentication is successful + * @event Client#authenticated + */ + this.emit(Events.AUTHENTICATED, authEventPayload); + const injected = await this.pupPage.evaluate(async () => { + return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined'; + }); + + if (!injected) { + if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) { + const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; + const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions); + + await webCache.persist(this.currentIndexHtml, version); } - 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'); + + if (isCometOrAbove) { + await this.pupPage.evaluate(ExposeStore); + } else { + // make sure all modules are ready before injection + // 2 second delay after authentication makes sense and does not need to be made dyanmic or removed + await new Promise(r => setTimeout(r, 2000)); + await this.pupPage.evaluate(ExposeLegacyStore); } - lOperand = lOperand.replace(/-beta$/, ''); - rOperand = rOperand.replace(/-beta$/, ''); + // Check window.Store Injection + await this.pupPage.waitForFunction('window.Store != undefined'); + + /** + * Current connection information + * @type {ClientInfo} + */ + this.info = new ClientInfo(this, await this.pupPage.evaluate(() => { + return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; + })); - while (lOperand.length !== rOperand.length) { - lOperand.length > rOperand.length - ? rOperand = rOperand.concat('0') - : lOperand = lOperand.concat('0'); - } + this.interface = new InterfaceController(this); - lOperand = Number(lOperand.replace(/\./g, '')); - rOperand = Number(rOperand.replace(/\./g, '')); + //Load util functions (serializers, helper functions) + await this.pupPage.evaluate(LoadUtils); - return ( - operator === '>' ? lOperand > rOperand : - operator === '>=' ? lOperand >= rOperand : - operator === '<' ? lOperand < rOperand : - operator === '<=' ? lOperand <= rOperand : - operator === '=' ? lOperand === rOperand : - false - ); - }; + await this.attachEventListeners(); + } + /** + * Emitted when the client has initialized and is ready to receive messages. + * @event Client#ready + */ + this.emit(Events.READY); + this.authStrategy.afterAuthReady(); + }); + let lastPercent = null; + await exposeFunctionIfAbsent(this.pupPage, 'onOfflineProgressUpdateEvent', async (percent) => { + if (lastPercent !== percent) { + lastPercent = percent; + this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now + } }); + await exposeFunctionIfAbsent(this.pupPage, 'onLogoutEvent', async () => { + this.lastLoggedOut = true; + await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _); + }); + await this.pupPage.evaluate(() => { + window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); }); + window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); }); + window.AuthStore.Cmd.on('offline_progress_update', () => { + window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress()); + }); + window.AuthStore.Cmd.on('logout', async () => { + await window.onLogoutEvent(); + }); + }); + } - await page.evaluate(ExposeStore, moduleRaid.toString()); - const authEventPayload = await this.authStrategy.getAuthEventPayload(); + /** + * Sets up events and requirements, kicks off authentication request + */ + async initialize() { - /** - * Emitted when authentication is successful - * @event Client#authenticated - */ - this.emit(Events.AUTHENTICATED, authEventPayload); + let + /** + * @type {puppeteer.Browser} + */ + browser, + /** + * @type {puppeteer.Page} + */ + page; - // Check window.Store Injection - await page.waitForFunction('window.Store != undefined'); + browser = null; + page = null; - await page.evaluate(async () => { - // safely unregister service workers - const registrations = await navigator.serviceWorker.getRegistrations(); - for (let registration of registrations) { - registration.unregister(); + await this.authStrategy.beforeBrowserInitialized(); + + const puppeteerOpts = this.options.puppeteer; + if (puppeteerOpts && puppeteerOpts.browserWSEndpoint) { + browser = await puppeteer.connect(puppeteerOpts); + page = await browser.newPage(); + } else { + const browserArgs = [...(puppeteerOpts.args || [])]; + if(!browserArgs.find(arg => arg.includes('--user-agent'))) { + browserArgs.push(`--user-agent=${this.options.userAgent}`); } + // navigator.webdriver fix + browserArgs.push('--disable-blink-features=AutomationControlled'); + + 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); + + this.pupBrowser = browser; + this.pupPage = page; + + await this.authStrategy.afterBrowserInitialized(); + await this.initWebVersionCache(); + + // ocVersion (isOfficialClient patch) + // remove after 2.3000.x hard release + await page.evaluateOnNewDocument(() => { + const originalError = Error; + window.originalError = originalError; + //eslint-disable-next-line no-global-assign + Error = function (message) { + const error = new originalError(message); + const originalStack = error.stack; + if (error.stack.includes('moduleRaid')) error.stack = originalStack + '\n at https://web.whatsapp.com/vendors~lazy_loaded_low_priority_components.05e98054dbd60f980427.js:2:44'; + return error; + }; + }); + + await page.goto(WhatsWebURL, { + waitUntil: 'load', + timeout: 0, + referer: 'https://whatsapp.com/' }); - //Load util functions (serializers, helper functions) - await page.evaluate(LoadUtils); + await this.inject(); - // Expose client info - /** - * Current connection information - * @type {ClientInfo} - */ - this.info = new ClientInfo(this, await page.evaluate(() => { - return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; - })); + this.pupPage.on('framenavigated', async (frame) => { + if(frame.url().includes('post_logout=1') || this.lastLoggedOut) { + this.emit(Events.DISCONNECTED, 'LOGOUT'); + await this.authStrategy.logout(); + await this.authStrategy.beforeBrowserInitialized(); + await this.authStrategy.afterBrowserInitialized(); + this.lastLoggedOut = false; + } + await this.inject(); + }); + } - // Add InterfaceController - this.interface = new InterfaceController(this); + /** + * Request authentication via pairing code instead of QR code + * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) + * @param {boolean} showNotification - Show notification to pair on phone number + * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" + */ + async requestPairingCode(phoneNumber, showNotification = true) { + return await this.pupPage.evaluate(async (phoneNumber, showNotification) => { + window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); + await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); + return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); + }, phoneNumber, showNotification); + } - // Register events - await page.exposeFunction('onAddMessageEvent', msg => { + /** + * Attach event listeners to WA Web + * Private function + * @property {boolean} reinject is this a reinject? + */ + async attachEventListeners() { + await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageEvent', msg => { if (msg.type === 'gp2') { const notification = new GroupNotification(this, msg); 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 - * @param {GroupNotification} notification GroupNotification with more information about the action - */ + * Emitted when a user joins the chat via invite link or is added by an admin. + * @event Client#group_join + * @param {GroupNotification} notification GroupNotification with more information about the action + */ this.emit(Events.GROUP_JOIN, notification); } else if (msg.subtype === 'remove' || msg.subtype === 'leave') { /** - * Emitted when a user leaves the chat or is removed by an admin. - * @event Client#group_leave - * @param {GroupNotification} notification GroupNotification with more information about the action - */ + * Emitted when a user leaves the chat or is removed by an admin. + * @event Client#group_leave + * @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 - */ + * 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 === 'membership_approval_request') { /** - * 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 - */ + * 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. - * @event Client#group_update - * @param {GroupNotification} notification GroupNotification with more information about the action - */ + * Emitted when group settings are updated, such as subject, description or picture. + * @event Client#group_update + * @param {GroupNotification} notification GroupNotification with more information about the action + */ this.emit(Events.GROUP_UPDATE, notification); } return; @@ -422,25 +409,25 @@ class Client extends EventEmitter { const message = new Message(this, msg); /** - * Emitted when a new message is created, which may include the current user's own messages. - * @event Client#message_create - * @param {Message} message The message that was created - */ + * Emitted when a new message is created, which may include the current user's own messages. + * @event Client#message_create + * @param {Message} message The message that was created + */ this.emit(Events.MESSAGE_CREATE, message); if (msg.id.fromMe) return; /** - * Emitted when a new message is received. - * @event Client#message - * @param {Message} message The message that was received - */ + * Emitted when a new message is received. + * @event Client#message + * @param {Message} message The message that was received + */ this.emit(Events.MESSAGE_RECEIVED, message); }); let last_message; - await page.exposeFunction('onChangeMessageTypeEvent', (msg) => { + await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageTypeEvent', (msg) => { if (msg.type === 'revoked') { const message = new Message(this, msg); @@ -450,33 +437,33 @@ class Client extends EventEmitter { } /** - * Emitted when a message is deleted for everyone in the chat. - * @event Client#message_revoke_everyone - * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data. - * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data. - * Note that due to the way this data is captured, it may be possible that this param will be undefined. - */ + * Emitted when a message is deleted for everyone in the chat. + * @event Client#message_revoke_everyone + * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data. + * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data. + * Note that due to the way this data is captured, it may be possible that this param will be undefined. + */ this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); } }); - await page.exposeFunction('onChangeMessageEvent', (msg) => { + await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageEvent', (msg) => { if (msg.type !== 'revoked') { last_message = msg; } /** - * The event notification that is received when one of - * the group participants changes their phone number. - */ + * 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. - */ + * 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) { @@ -487,75 +474,74 @@ class Client extends EventEmitter { 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. - */ + * 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) => { + await exposeFunctionIfAbsent(this.pupPage, 'onRemoveMessageEvent', (msg) => { if (!msg.isNewMsg) return; const message = new Message(this, msg); /** - * Emitted when a message is deleted by the current user. - * @event Client#message_revoke_me - * @param {Message} message The message that was revoked - */ + * Emitted when a message is deleted by the current user. + * @event Client#message_revoke_me + * @param {Message} message The message that was revoked + */ this.emit(Events.MESSAGE_REVOKED_ME, message); }); - await page.exposeFunction('onMessageAckEvent', (msg, ack) => { + await exposeFunctionIfAbsent(this.pupPage, 'onMessageAckEvent', (msg, ack) => { const message = new Message(this, msg); /** - * Emitted when an ack event occurrs on message type. - * @event Client#message_ack - * @param {Message} message The message that was affected - * @param {MessageAck} ack The new ACK value - */ + * Emitted when an ack event occurrs on message type. + * @event Client#message_ack + * @param {Message} message The message that was affected + * @param {MessageAck} ack The new ACK value + */ this.emit(Events.MESSAGE_ACK, message, ack); }); - await page.exposeFunction('onChatUnreadCountEvent', async (data) =>{ + await exposeFunctionIfAbsent(this.pupPage, 'onChatUnreadCountEvent', async (data) =>{ const chat = await this.getChatById(data.id); - + /** - * Emitted when the chat unread count changes - */ + * Emitted when the chat unread count changes + */ this.emit(Events.UNREAD_COUNT, chat); }); - await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => { + await exposeFunctionIfAbsent(this.pupPage, 'onMessageMediaUploadedEvent', (msg) => { const message = new Message(this, msg); /** - * Emitted when media has been uploaded for a message sent by the client. - * @event Client#media_uploaded - * @param {Message} message The message with media that was uploaded - */ + * Emitted when media has been uploaded for a message sent by the client. + * @event Client#media_uploaded + * @param {Message} message The message with media that was uploaded + */ this.emit(Events.MEDIA_UPLOADED, message); }); - await page.exposeFunction('onAppStateChangedEvent', async (state) => { - + await exposeFunctionIfAbsent(this.pupPage, 'onAppStateChangedEvent', async (state) => { /** - * Emitted when the connection state changes - * @event Client#change_state - * @param {WAState} state the new connection state - */ + * Emitted when the connection state changes + * @event Client#change_state + * @param {WAState} state the new connection state + */ this.emit(Events.STATE_CHANGED, state); const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT]; @@ -572,121 +558,121 @@ class Client extends EventEmitter { if (!ACCEPTED_STATES.includes(state)) { /** - * Emitted when the client has been disconnected - * @event Client#disconnected - * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect - */ + * Emitted when the client has been disconnected + * @event Client#disconnected + * @param {WAState|"LOGOUT"} reason reason that caused the disconnect + */ await this.authStrategy.disconnect(); this.emit(Events.DISCONNECTED, state); this.destroy(); } }); - await page.exposeFunction('onBatteryStateChangedEvent', (state) => { + await exposeFunctionIfAbsent(this.pupPage, 'onBatteryStateChangedEvent', (state) => { const { battery, plugged } = state; if (battery === undefined) return; /** - * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. - * @event Client#change_battery - * @param {object} batteryInfo - * @param {number} batteryInfo.battery - The current battery percentage - * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) - * @deprecated - */ + * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. + * @event Client#change_battery + * @param {object} batteryInfo + * @param {number} batteryInfo.battery - The current battery percentage + * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) + * @deprecated + */ this.emit(Events.BATTERY_CHANGED, { battery, plugged }); }); - await page.exposeFunction('onIncomingCall', (call) => { + await exposeFunctionIfAbsent(this.pupPage, 'onIncomingCall', (call) => { /** - * Emitted when a call is received - * @event Client#incoming_call - * @param {object} call - * @param {number} call.id - Call id - * @param {string} call.peerJid - Who called - * @param {boolean} call.isVideo - if is video - * @param {boolean} call.isGroup - if is group - * @param {boolean} call.canHandleLocally - if we can handle in waweb - * @param {boolean} call.outgoing - if is outgoing - * @param {boolean} call.webClientShouldHandle - If Waweb should handle - * @param {object} call.participants - Participants - */ + * Emitted when a call is received + * @event Client#incoming_call + * @param {object} call + * @param {number} call.id - Call id + * @param {string} call.peerJid - Who called + * @param {boolean} call.isVideo - if is video + * @param {boolean} call.isGroup - if is group + * @param {boolean} call.canHandleLocally - if we can handle in waweb + * @param {boolean} call.outgoing - if is outgoing + * @param {boolean} call.webClientShouldHandle - If Waweb should handle + * @param {object} call.participants - Participants + */ const cll = new Call(this, call); this.emit(Events.INCOMING_CALL, cll); }); - await page.exposeFunction('onReaction', (reactions) => { + await exposeFunctionIfAbsent(this.pupPage, '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 - */ + * 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) => { + await exposeFunctionIfAbsent(this.pupPage, 'onRemoveChatEvent', async (chat) => { const _chat = await this.getChatById(chat.id); /** - * Emitted when a chat is removed - * @event Client#chat_removed - * @param {Chat} chat - */ + * 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); + await exposeFunctionIfAbsent(this.pupPage, '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 - */ + * 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) => { - + await exposeFunctionIfAbsent(this.pupPage, '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 - */ + * 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.exposeFunction('onAddMessageCiphertextEvent', msg => { + await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageCiphertextEvent', msg => { + /** - * Emitted when messages are edited - * @event Client#message_ciphertext - * @param {Message} message - */ + * Emitted when messages are edited + * @event Client#message_ciphertext + * @param {Message} message + */ this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); }); - await page.exposeFunction('onPollVoteEvent', (vote) => { + await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (vote) => { const _vote = new PollVote(this, vote); /** * Emitted when some poll option is selected or deselected, @@ -696,7 +682,7 @@ class Client extends EventEmitter { this.emit(Events.VOTE_UPDATE, _vote); }); - await page.evaluate(() => { + await this.pupPage.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); }); @@ -720,12 +706,28 @@ class Client extends EventEmitter { } }); window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);}); - window.Store.PollVote.on('add', (vote) => { - const pollVoteModel = window.WWebJS.getPollVoteModel(vote); + window.Store.PollVote.on('add', async (vote) => { + const pollVoteModel = await window.WWebJS.getPollVoteModel(vote); pollVoteModel && window.onPollVoteEvent(pollVoteModel); }); - { + if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) { + const module = window.Store.AddonReactionTable; + const ogMethod = module.bulkUpsert; + module.bulkUpsert = ((...args) => { + window.onReaction(args[0].map(reaction => { + const msgKey = reaction.id; + const parentMsgKey = reaction.reactionParentKey; + const timestamp = reaction.reactionTimestamp / 1000; + const sender = reaction.author ?? reaction.from; + const senderUserJid = sender._serialized; + + return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp }; + })); + + return ogMethod(...args); + }).bind(module); + } else { const module = window.Store.createOrUpdateReactionsModule; const ogMethod = module.createOrUpdateReactions; module.createOrUpdateReactions = ((...args) => { @@ -741,24 +743,7 @@ class Client extends EventEmitter { }).bind(module); } }); - - /** - * Emitted when the client has initialized and is ready to receive messages. - * @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; @@ -783,7 +768,8 @@ class Client extends EventEmitter { } else { this.pupPage.on('response', async (res) => { if(res.ok() && res.url() === WhatsWebURL) { - await webCache.persist(await res.text()); + const indexHtml = await res.text(); + this.currentIndexHtml = indexHtml; } }); } @@ -802,7 +788,9 @@ class Client extends EventEmitter { */ async logout() { await this.pupPage.evaluate(() => { - return window.Store.AppState.logout(); + if (window.Store && window.Store.AppState && typeof window.Store.AppState.logout === 'function') { + return window.Store.AppState.logout(); + } }); await this.pupBrowser.close(); @@ -861,6 +849,7 @@ class Client extends EventEmitter { * @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions * @property {string[]} [mentions] - User IDs to mention in the message * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message + * @property {string} [invokedBotWid=undefined] - Bot Wid when doing a bot mention like @Meta AI * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true). * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true). * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null. @@ -894,9 +883,10 @@ class Client extends EventEmitter { sendMediaAsDocument: options.sendMediaAsDocument, caption: options.caption, quotedMessageId: options.quotedMessageId, - parseVCards: options.parseVCards === false ? false : true, + parseVCards: options.parseVCards !== false, mentionedJidList: options.mentions || [], groupMentions: options.groupMentions, + invokedBotWid: options.invokedBotWid, extraOptions: options.extra }; @@ -1032,7 +1022,7 @@ class Client extends EventEmitter { if(msg) return window.WWebJS.getMessageModel(msg); const params = messageId.split('_'); - if(params.length !== 3) throw new Error('Invalid serialized message id specified'); + if (params.length !== 3 && params.length !== 4) 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]; @@ -1055,20 +1045,20 @@ class Client extends EventEmitter { const chat = await window.Store.Chat.find(chatWid); if (!chat) return []; - await window.Store.PinnedMsgUtils.seekAndDestroyExpiredPins( + /* await window.Store.PinnedMsgUtils.seekAndDestroyExpiredPins( window.Store.PinnedMsgUtils.PinInChatCollection.byChatId(chatWid).toArray() - ).catch((_) => _); + ).catch((_) => _); */ - const msgs = await window.Store.PinnedMsgUtils.getPinInChatByChatId(chatWid); - window.Store.PinnedMsgUtils.PinInChatCollection.add( + const msgs = await window.Store.PinnedMsgUtils.getTable().equals(['chatId'], chatWid.toString()); + /* window.Store.PinnedMsgUtils.PinInChatCollection.add( msgs.map(window.Store.PinnedMsgUtils.createPinInChatModel) - ); + ); */ - for (const msg of window.Store.PinnedMsgUtils.PinInChatCollection.getModelsArray()) { + /* for (const msg of window.Store.PinnedMsgUtils.PinInChatCollection.getModelsArray()) { await window.Store.MsgCollection.hydrateOrGetMessages([msg.parentMsgKey.toString()]).catch((_) => _); - } + } */ - const pinnedMsgs = msgs.map((msg) => window.Store.Msg.get(msg.parentMsgKey._serialized)); + const pinnedMsgs = msgs.map((msg) => window.Store.Msg.get(msg.parentMsgKey/* ._serialized */)); return !pinnedMsgs.length ? [] @@ -1136,14 +1126,8 @@ class Client extends EventEmitter { async setDisplayName(displayName) { const couldSet = await this.pupPage.evaluate(async displayName => { if(!window.Store.Conn.canSetMyPushname()) return false; - - if(window.Store.MDBackend) { - await window.Store.Settings.setPushname(displayName); - return true; - } else { - const res = await window.Store.Wap.setPushname(displayName); - return !res.status || res.status === 200; - } + await window.Store.Settings.setPushname(displayName); + return true; }, displayName); return couldSet; @@ -1284,7 +1268,9 @@ class Client extends EventEmitter { const profilePic = await this.pupPage.evaluate(async contactId => { try { const chatWid = window.Store.WidFactory.createWid(contactId); - return await window.Store.ProfilePic.profilePicFind(chatWid); + return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0') + ? await window.Store.ProfilePic.profilePicFind(chatWid) + : await window.Store.ProfilePic.requestProfilePicFromServer(chatWid); } catch (err) { if(err.name === 'ServerStatusCodeError') return undefined; throw err; @@ -1450,10 +1436,18 @@ class Client extends EventEmitter { try { createGroupResult = await window.Store.GroupUtils.createGroup( - title, - participantWids, - messageTimer, - parentGroupWid + { + 'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode, + 'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode, + 'announce': options.announce === undefined ? true : options.announce, + 'ephemeralDuration': messageTimer, + 'full': undefined, + 'parentGroupId': parentGroupWid, + 'restrict': options.restrict === undefined ? true : options.restrict, + 'thumb': undefined, + 'title': title, + }, + participantWids ); } catch (err) { return 'CreateGroupError: An unknown error occupied while creating a group'; @@ -1465,7 +1459,7 @@ class Client extends EventEmitter { const statusCode = participant.error ?? 200; if (autoSendInviteV4 && statusCode === 403) { - window.Store.ContactCollection.gadd(participant.wid, { silent: true }); + window.Store.Contact.gadd(participant.wid, { silent: true }); const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage( await window.Store.Chat.find(participant.wid), createGroupResult.wid._serialized, @@ -1512,6 +1506,17 @@ class Client extends EventEmitter { return labels.map(data => new Label(this, data)); } + + /** + * Get all current Broadcast + * @returns {Promise>} + */ + async getBroadcasts() { + const broadcasts = await this.pupPage.evaluate(async () => { + return window.WWebJS.getAllStatuses(); + }); + return broadcasts.map(data => new Broadcast(this, data)); + } /** * Get Label instance by ID @@ -1642,8 +1647,8 @@ class Client extends EventEmitter { * @returns {Promise>} An array of membership requests */ async getGroupMembershipRequests(groupId) { - return await this.pupPage.evaluate(async (gropId) => { - const groupWid = window.Store.WidFactory.createWid(gropId); + return await this.pupPage.evaluate(async (groupId) => { + const groupWid = window.Store.WidFactory.createWid(groupId); return await window.Store.MembershipRequestUtils.getMembershipApprovalRequests(groupWid); }, groupId); } @@ -1749,6 +1754,41 @@ class Client extends EventEmitter { return flag; }, flag); } + + /** + * Get user device count by ID + * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one + * So for a non-enterprise user with one WaWeb connection it should return "2" + * @param {string} userId + * @returns {Promise} + */ + async getContactDeviceCount(userId) { + return await this.pupPage.evaluate(async (userId) => { + const devices = await window.Store.DeviceList.getDeviceIds([window.Store.WidFactory.createWid(userId)]); + if (devices && devices.length && devices[0] != null && typeof devices[0].devices == 'object') { + return devices[0].devices.length; + } + return 0; + }, userId); + } + + /** + * Sync chat history conversation + * @param {string} chatId + * @return {Promise} True if operation completed successfully, false otherwise. + */ + async syncHistory(chatId) { + return await this.pupPage.evaluate(async (chatId) => { + const chat = await window.WWebJS.getChat(chatId); + if (chat.endOfHistoryTransferType === 0) { + await window.Store.HistorySync.sendPeerDataOperationRequest(3, { + chatId: chat.id + }); + return true; + } + return false; + }, chatId); + } } module.exports = Client; diff --git a/src/authStrategies/LegacySessionAuth.js b/src/authStrategies/LegacySessionAuth.js deleted file mode 100644 index dd09f7b5e6..0000000000 --- a/src/authStrategies/LegacySessionAuth.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -const BaseAuthStrategy = require('./BaseAuthStrategy'); - -/** - * Legacy session auth strategy - * Not compatible with multi-device accounts. - * @param {object} options - options - * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails - * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session - * @param {string} options.session.WABrowserId - * @param {string} options.session.WASecretBundle - * @param {string} options.session.WAToken1 - * @param {string} options.session.WAToken2 - */ -class LegacySessionAuth extends BaseAuthStrategy { - constructor({ session, restartOnAuthFail }={}) { - super(); - this.session = session; - this.restartOnAuthFail = restartOnAuthFail; - } - - async afterBrowserInitialized() { - if(this.session) { - await this.client.pupPage.evaluateOnNewDocument(session => { - if (document.referrer === 'https://whatsapp.com/') { - localStorage.clear(); - localStorage.setItem('WABrowserId', session.WABrowserId); - localStorage.setItem('WASecretBundle', session.WASecretBundle); - localStorage.setItem('WAToken1', session.WAToken1); - localStorage.setItem('WAToken2', session.WAToken2); - } - - localStorage.setItem('remember-me', 'true'); - }, this.session); - } - } - - async onAuthenticationNeeded() { - if(this.session) { - this.session = null; - return { - failed: true, - restart: this.restartOnAuthFail, - failureEventPayload: 'Unable to log in. Are the session details valid?' - }; - } - - return { failed: false }; - } - - async getAuthEventPayload() { - const isMD = await this.client.pupPage.evaluate(() => { - return window.Store.MDBackend; - }); - - if(isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.'); - - const localStorage = JSON.parse(await this.client.pupPage.evaluate(() => { - return JSON.stringify(window.localStorage); - })); - - return { - WABrowserId: localStorage.WABrowserId, - WASecretBundle: localStorage.WASecretBundle, - WAToken1: localStorage.WAToken1, - WAToken2: localStorage.WAToken2 - }; - } -} - -module.exports = LegacySessionAuth; diff --git a/src/authStrategies/LocalAuth.js b/src/authStrategies/LocalAuth.js index 8309c3d0d5..543a6b9ba1 100644 --- a/src/authStrategies/LocalAuth.js +++ b/src/authStrategies/LocalAuth.js @@ -44,7 +44,10 @@ class LocalAuth extends BaseAuthStrategy { async logout() { if (this.userDataDir) { - return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true, force: true }); + await fs.promises.rm(this.userDataDir, { recursive: true, force: true }) + .catch((e) => { + throw new Error(e); + }); } } diff --git a/src/structures/Broadcast.js b/src/structures/Broadcast.js new file mode 100644 index 0000000000..eb7f97765f --- /dev/null +++ b/src/structures/Broadcast.js @@ -0,0 +1,69 @@ +'use strict'; + +const Base = require('./Base'); +const Message = require('./Message'); + +/** + * Represents a Status/Story on WhatsApp + * @extends {Base} + */ +class Broadcast extends Base { + constructor(client, data) { + super(client); + + if (data) this._patch(data); + } + + _patch(data) { + /** + * ID that represents the chat + * @type {object} + */ + this.id = data.id; + + /** + * Unix timestamp of last status + * @type {number} + */ + this.timestamp = data.t; + + /** + * Number of available statuses + * @type {number} + */ + this.totalCount = data.totalCount; + + /** + * Number of not viewed + * @type {number} + */ + this.unreadCount = data.unreadCount; + + /** + * Messages statuses + * @type {Message[]} + */ + this.msgs = data.msgs.map(msg => new Message(this.client, msg)); + + return super._patch(data); + } + + /** + * Returns the Chat this message was sent in + * @returns {Promise} + */ + getChat() { + return this.client.getChatById(this.id._serialized); + } + + /** + * Returns the Contact this message was sent from + * @returns {Promise} + */ + getContact() { + return this.client.getContactById(this.id._serialized); + } + +} + +module.exports = Broadcast; diff --git a/src/structures/Chat.js b/src/structures/Chat.js index 718f879f5a..516de50dd4 100644 --- a/src/structures/Chat.js +++ b/src/structures/Chat.js @@ -79,7 +79,7 @@ class Chat extends Base { * Last message fo chat * @type {Message} */ - this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined; + this.lastMessage = data.lastMessage ? new Message(this.client, data.lastMessage) : undefined; return super._patch(data); } @@ -278,6 +278,14 @@ class Chat extends Base { async getPinnedMessages() { return this.client.getPinnedMessages(this.id._serialized); } + + /** + * Sync chat history conversation + * @return {Promise} True if operation completed successfully, false otherwise. + */ + async syncHistory() { + return this.client.syncHistory(this.id._serialized); + } } module.exports = Chat; diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 4ae5b19559..04de9bdf0b 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -98,7 +98,7 @@ class GroupChat extends Chat { 419: 'The participant can\'t be added because the group is full' }; - await window.Store.GroupMetadata.queryAndUpdate(groupWid); + await window.Store.GroupQueryAndUpdate(groupWid); const groupMetadata = group.groupMetadata; const groupParticipants = groupMetadata?.participants; @@ -152,7 +152,7 @@ class GroupChat extends Chat { if (autoSendInviteV4 && rpcResultCode === 403) { let userChat, isInviteV4Sent = false; - window.Store.ContactCollection.gadd(pWid, { silent: true }); + window.Store.Contact.gadd(pWid, { silent: true }); if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' && (userChat = await window.Store.Chat.find(pWid))) { @@ -380,10 +380,18 @@ class GroupChat extends Chat { async getInviteCode() { const codeRes = await this.client.pupPage.evaluate(async chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.GroupInvite.queryGroupInviteCode(chatWid); + try { + return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') + ? await window.Store.GroupInvite.queryGroupInviteCode(chatWid, true) + : await window.Store.GroupInvite.queryGroupInviteCode(chatWid); + } + catch (err) { + if(err.name === 'ServerStatusCodeError') return undefined; + throw err; + } }, this.id._serialized); - return codeRes.code; + return codeRes?.code; } /** diff --git a/src/structures/Message.js b/src/structures/Message.js index b61563af68..6013063dd4 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -182,8 +182,8 @@ class Message extends Base { inviteCodeExp: data.inviteCodeExp, groupId: data.inviteGrp, groupName: data.inviteGrpName, - fromId: '_serialized' in data.from ? data.from._serialized : data.from, - toId: '_serialized' in data.to ? data.to._serialized : data.to + fromId: typeof data.from === 'object' && '_serialized' in data.from ? data.from._serialized : data.from, + toId: typeof data.to === 'object' && '_serialized' in data.to ? data.to._serialized : data.to } : undefined; /** @@ -311,9 +311,9 @@ class Message extends Base { * @returns {Promise} */ async reload() { - const newData = await this.client.pupPage.evaluate((msgId) => { - const msg = window.Store.Msg.get(msgId); - if(!msg) return null; + const newData = await this.client.pupPage.evaluate(async (msgId) => { + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + if (!msg) return null; return window.WWebJS.getMessageModel(msg); }, this.id._serialized); @@ -370,8 +370,8 @@ class Message extends Base { async getQuotedMessage() { if (!this.hasQuotedMsg) return undefined; - const quotedMsg = await this.client.pupPage.evaluate((msgId) => { - const msg = window.Store.Msg.get(msgId); + const quotedMsg = await this.client.pupPage.evaluate(async (msgId) => { + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; const quotedMsg = window.Store.QuotedMsg.getQuotedMsgObj(msg); return window.WWebJS.getMessageModel(quotedMsg); }, this.id._serialized); @@ -409,9 +409,10 @@ class Message extends Base { */ async react(reaction){ await this.client.pupPage.evaluate(async (messageId, reaction) => { - if (!messageId) { return undefined; } - - const msg = await window.Store.Msg.get(messageId); + if (!messageId) return null; + const msg = + window.Store.Msg.get(messageId) || (await window.Store.Msg.getMessagesById([messageId]))?.messages?.[0]; + if(!msg) return null; await window.Store.sendReactionToMsg(msg, reaction); }, this.id._serialized, reaction); } @@ -434,10 +435,7 @@ class Message extends Base { const chatId = typeof chat === 'string' ? chat : chat.id._serialized; await this.client.pupPage.evaluate(async (msgId, chatId) => { - let msg = window.Store.Msg.get(msgId); - let chat = window.Store.Chat.get(chatId); - - return await chat.forwardMessages([msg]); + return window.WWebJS.forwardMessage(chatId, msgId); }, this.id._serialized, chatId); } @@ -451,9 +449,9 @@ class Message extends Base { } const result = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (!msg || !msg.mediaData) { - return undefined; + return null; } if (msg.mediaData.mediaStage != 'RESOLVED') { // try to resolve media @@ -503,12 +501,16 @@ class Message extends Base { */ async delete(everyone) { await this.client.pupPage.evaluate(async (msgId, everyone) => { - let msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; let chat = await window.Store.Chat.find(msg.id.remote); const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg); if (everyone && canRevoke) { - return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); + if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')) { + return window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: true }); + } else { + return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); + } } return window.Store.Cmd.sendDeleteMsgs(chat, [msg], true); @@ -520,8 +522,7 @@ class Message extends Base { */ async star() { await this.client.pupPage.evaluate(async (msgId) => { - let msg = window.Store.Msg.get(msgId); - + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (window.Store.MsgActionChecks.canStarMsg(msg)) { let chat = await window.Store.Chat.find(msg.id.remote); return window.Store.Cmd.sendStarMsgs(chat, [msg], false); @@ -534,8 +535,7 @@ class Message extends Base { */ async unstar() { await this.client.pupPage.evaluate(async (msgId) => { - let msg = window.Store.Msg.get(msgId); - + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (window.Store.MsgActionChecks.canStarMsg(msg)) { let chat = await window.Store.Chat.find(msg.id.remote); return window.Store.Cmd.sendUnstarMsgs(chat, [msg], false); @@ -582,7 +582,7 @@ class Message extends Base { */ async getInfo() { const info = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (!msg || !msg.id.fromMe) return null; return new Promise((resolve) => { @@ -616,7 +616,7 @@ class Message extends Base { async getPayment() { if (this.type === MessageTypes.PAYMENT) { const msg = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if(!msg) return null; return msg.serialize(); }, this.id._serialized); @@ -691,11 +691,11 @@ class Message extends Base { return null; } const messageEdit = await this.client.pupPage.evaluate(async (msgId, message, options) => { - let msg = window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (!msg) return null; - let catEdit = window.Store.MsgActionChecks.canEditText(msg) || window.Store.MsgActionChecks.canEditCaption(msg); - if (catEdit) { + let canEdit = window.Store.MsgActionChecks.canEditText(msg) || window.Store.MsgActionChecks.canEditCaption(msg); + if (canEdit) { const msgEdit = await window.WWebJS.editMessage(msg, message, options); return msgEdit.serialize(); } diff --git a/src/structures/index.js b/src/structures/index.js index 5568a87847..a3abc6fd24 100644 --- a/src/structures/index.js +++ b/src/structures/index.js @@ -20,5 +20,6 @@ module.exports = { Payment: require('./Payment'), Reaction: require('./Reaction'), Poll: require('./Poll'), - PollVote: require('./PollVote') + PollVote: require('./PollVote'), + Broadcast: require('./Broadcast') }; diff --git a/src/util/Constants.js b/src/util/Constants.js index e8c5fe3e08..9f334488b1 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -7,7 +7,7 @@ exports.DefaultOptions = { headless: true, defaultViewport: null }, - webVersion: '2.2346.52', + webVersion: '2.3000.1016590837', webVersionCache: { type: 'local', }, diff --git a/src/util/Injected/AuthStore/AuthStore.js b/src/util/Injected/AuthStore/AuthStore.js new file mode 100644 index 0000000000..c797aa0544 --- /dev/null +++ b/src/util/Injected/AuthStore/AuthStore.js @@ -0,0 +1,17 @@ +'use strict'; + +exports.ExposeAuthStore = () => { + window.AuthStore = {}; + window.AuthStore.AppState = window.require('WAWebSocketModel').Socket; + window.AuthStore.Cmd = window.require('WAWebCmd').Cmd; + window.AuthStore.Conn = window.require('WAWebConnModel').Conn; + window.AuthStore.OfflineMessageHandler = window.require('WAWebOfflineHandler').OfflineMessageHandler; + window.AuthStore.PairingCodeLinkUtils = window.require('WAWebAltDeviceLinkingApi'); + window.AuthStore.Base64Tools = window.require('WABase64'); + window.AuthStore.RegistrationUtils = { + ...window.require('WAWebCompanionRegClientUtils'), + ...window.require('WAWebAdvSignatureApi'), + ...window.require('WAWebUserPrefsInfoStore'), + ...window.require('WAWebSignalStoreApi'), + }; +}; \ No newline at end of file diff --git a/src/util/Injected/AuthStore/LegacyAuthStore.js b/src/util/Injected/AuthStore/LegacyAuthStore.js new file mode 100644 index 0000000000..c3016f7d17 --- /dev/null +++ b/src/util/Injected/AuthStore/LegacyAuthStore.js @@ -0,0 +1,22 @@ +'use strict'; + +//TODO: To be removed by version 2.3000.x hard release + +exports.ExposeLegacyAuthStore = (moduleRaidStr) => { + eval('var moduleRaid = ' + moduleRaidStr); + // eslint-disable-next-line no-undef + window.mR = moduleRaid(); + window.AuthStore = {}; + window.AuthStore.AppState = window.mR.findModule('Socket')[0].Socket; + window.AuthStore.Cmd = window.mR.findModule('Cmd')[0].Cmd; + window.AuthStore.Conn = window.mR.findModule('Conn')[0].Conn; + window.AuthStore.OfflineMessageHandler = window.mR.findModule('OfflineMessageHandler')[0].OfflineMessageHandler; + window.AuthStore.PairingCodeLinkUtils = window.mR.findModule('initializeAltDeviceLinking')[0]; + window.AuthStore.Base64Tools = window.mR.findModule('encodeB64')[0]; + window.AuthStore.RegistrationUtils = { + ...window.mR.findModule('getCompanionWebClientFromBrowser')[0], + ...window.mR.findModule('verifyKeyIndexListAccountSignature')[0], + ...window.mR.findModule('waNoiseInfo')[0], + ...window.mR.findModule('waSignalStore')[0], + }; +}; \ No newline at end of file diff --git a/src/util/Injected/LegacyStore.js b/src/util/Injected/LegacyStore.js new file mode 100644 index 0000000000..e9584d9b7a --- /dev/null +++ b/src/util/Injected/LegacyStore.js @@ -0,0 +1,146 @@ +'use strict'; + +//TODO: To be removed by version 2.3000.x hard release + +// Exposes the internal Store to the WhatsApp Web client +exports.ExposeLegacyStore = () => { + window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); + window.Store.AppState = window.mR.findModule('Socket')[0].Socket; + window.Store.Conn = window.mR.findModule('Conn')[0].Conn; + window.Store.BlockContact = window.mR.findModule('blockContact')[0]; + window.Store.Call = window.mR.findModule((module) => module.default && 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.GroupQueryAndUpdate = window.mR.findModule('queryAndUpdateGroupMetadataById')[0].queryAndUpdateGroupMetadataById; + window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; + 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 && module.default.fromString)[0].default; + window.Store.OpaqueData = window.mR.findModule(module => module.default && 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.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; + window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default; + window.Store.Validators = window.mR.findModule('findLinks')[0]; + window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; + window.Store.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.LinkPreview = window.mR.findModule('getLinkPreview')[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]; + window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo; + window.Store.pinUnpinMsg = window.mR.findModule('sendPinInChatMsg')[0].sendPinInChatMsg; + + /* 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 && m[0]; + /* eslint-enable no-undef, no-cond-assign */ + + window.Store.Settings = { + ...window.mR.findModule('ChatlistPanelState')[0], + setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname + }; + 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]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; + + const _isMDBackend = window.mR.findModule('isMDBackend'); + if(_isMDBackend && _isMDBackend[0] && _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} module The name or a key of the target module to search + * @property {number} index The index value of the target module + * @property {string} function 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.module === 'string' + ? window.mR.findModule(target.module) + : window.mR.modules[target.module]; + const originalFunction = module[target.index][target.function]; + const modifiedFunction = (...args) => callback(originalFunction, ...args); + module[target.index][target.function] = modifiedFunction; + }; + + window.injectToFunction({ module: 'mediaTypeFromProtobuf', index: 0, function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); + + window.injectToFunction({ module: 'typeAttributeFromProtobuf', index: 0, function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); +}; \ No newline at end of file diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js new file mode 100644 index 0000000000..f0122fc340 --- /dev/null +++ b/src/util/Injected/Store.js @@ -0,0 +1,172 @@ +'use strict'; + +exports.ExposeStore = () => { + /** + * 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 + ); + }; + + window.Store = Object.assign({}, window.require('WAWebCollections')); + window.Store.AppState = window.require('WAWebSocketModel').Socket; + window.Store.BlockContact = window.require('WAWebBlockContactAction'); + window.Store.Conn = window.require('WAWebConnModel').Conn; + window.Store.Cmd = window.require('WAWebCmd').Cmd; + window.Store.DownloadManager = window.require('WAWebDownloadManager').downloadManager; + window.Store.GroupQueryAndUpdate = window.require('WAWebGroupQueryJob').queryAndUpdateGroupMetadataById; + window.Store.MediaPrep = window.require('WAWebPrepRawMedia'); + window.Store.MediaObject = window.require('WAWebMediaStorage'); + window.Store.MediaTypes = window.require('WAWebMmsMediaTypes'); + window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload'); + window.Store.MsgKey = window.require('WAWebMsgKey'); + window.Store.NumberInfo = window.require('WAPhoneUtils'); + window.Store.OpaqueData = window.require('WAWebMediaOpaqueData'); + window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge'); + window.Store.QueryOrder = window.require('WAWebBizOrderBridge'); + window.Store.SendClear = window.require('WAWebChatClearBridge'); + window.Store.SendDelete = window.require('WAWebDeleteChatAction'); + window.Store.SendMessage = window.require('WAWebSendMsgChatAction'); + window.Store.EditMessage = window.require('WAWebSendMessageEditAction'); + window.Store.SendSeen = window.require('WAWebUpdateUnreadChatAction'); + window.Store.User = window.require('WAWebUserPrefsMeUser'); + window.Store.ContactMethods = window.require('WAWebContactGetters'); + window.Store.UploadUtils = window.require('WAWebUploadManager'); + window.Store.UserConstructor = window.require('WAWebWid'); + window.Store.Validators = window.require('WALinkify'); + window.Store.VCard = window.require('WAWebFrontendVcardUtils'); + window.Store.WidFactory = window.require('WAWebWidFactory'); + window.Store.ProfilePic = window.require('WAWebContactProfilePicThumbBridge'); + window.Store.PresenceUtils = window.require('WAWebPresenceChatAction'); + window.Store.ChatState = window.require('WAWebChatStateBridge'); + window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups; + window.Store.StatusUtils = window.require('WAWebContactStatusBridge'); + window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages'); + window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg; + window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions'); + window.Store.EphemeralFields = window.require('WAWebGetEphemeralFieldsMsgActionsUtils'); + window.Store.MsgActionChecks = window.require('WAWebMsgActionCapability'); + window.Store.QuotedMsg = window.require('WAWebQuotedMsgModelUtils'); + window.Store.LinkPreview = window.require('WAWebLinkPreviewChatAction'); + window.Store.Socket = window.require('WADeprecatedSendIq'); + window.Store.SocketWap = window.require('WAWap'); + window.Store.SearchContext = window.require('WAWebChatMessageSearch').getSearchContext; + window.Store.DrawerManager = window.require('WAWebDrawerManager').DrawerManager; + window.Store.LidUtils = window.require('WAWebApiContact'); + window.Store.WidToJid = window.require('WAWebWidToJid'); + window.Store.JidToWid = window.require('WAWebJidToWid'); + window.Store.getMsgInfo = window.require('WAWebApiMessageInfoStore').queryMsgInfo; + window.Store.pinUnpinMsg = window.require('WAWebSendPinMessageAction').sendPinInChatMsg; + window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists; + window.Store.ReplyUtils = window.require('WAWebMsgReply'); + window.Store.Settings = window.require('WAWebUserPrefsGeneral'); + window.Store.BotSecret = window.require('WAWebBotMessageSecret'); + window.Store.BotProfiles = window.require('WAWebBotProfileCollection'); + window.Store.DeviceList = window.require('WAWebApiDeviceList'); + window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest'); + if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) + window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode; + + window.Store.ForwardUtils = { + ...window.require('WAWebForwardMessagesToChat') + }; + window.Store.PinnedMsgUtils = { + /* ...window.require('WAWebPinInChatCollection'), + ...window.require('WAWebPinMessageAction'), */ + ...window.require('WAWebPinInChatSchema') + }; + window.Store.StickerTools = { + ...window.require('WAWebImageUtils'), + ...window.require('WAWebAddWebpMetadata') + }; + window.Store.GroupUtils = { + ...window.require('WAWebGroupCreateJob'), + ...window.require('WAWebGroupModifyInfoJob'), + ...window.require('WAWebExitGroupAction'), + ...window.require('WAWebContactProfilePicThumbBridge') + }; + window.Store.GroupParticipants = { + ...window.require('WAWebModifyParticipantsGroupAction'), + ...window.require('WASmaxGroupsAddParticipantsRPC') + }; + window.Store.GroupInvite = { + ...window.require('WAWebGroupInviteJob'), + ...window.require('WAWebGroupQueryJob') + }; + window.Store.GroupInviteV4 = { + ...window.require('WAWebGroupInviteV4Job'), + ...window.require('WAWebChatSendMessages') + }; + window.Store.MembershipRequestUtils = { + ...window.require('WAWebApiMembershipApprovalRequestStore'), + ...window.require('WASmaxGroupsMembershipRequestsActionRPC') + }; + + if (!window.Store.Chat._find || !window.Store.Chat.findImpl) { + window.Store.Chat._find = e => { + const target = window.Store.Chat.get(e); + return target ? Promise.resolve(target) : Promise.resolve({ + id: e + }); + }; + window.Store.Chat.findImpl = window.Store.Chat._find; + } + + /** + * Target options object description + * @typedef {Object} TargetOptions + * @property {string|number} module The target module + * @property {string} function 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 = window.require(target.module); + const originalFunction = module[target.function]; + const modifiedFunction = (...args) => callback(originalFunction, ...args); + module[target.function] = modifiedFunction; + }; + + window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); + + window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); + +}; diff --git a/src/util/Injected.js b/src/util/Injected/Utils.js similarity index 77% rename from src/util/Injected.js rename to src/util/Injected/Utils.js index 0ec37ba47a..f48eb36daf 100644 --- a/src/util/Injected.js +++ b/src/util/Injected/Utils.js @@ -1,162 +1,19 @@ '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 && 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 && 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.MsgCollection = window.mR.findModule('MsgCollection')[0].MsgCollection; - 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 && module.default.fromString)[0].default; - window.Store.OpaqueData = window.mR.findModule(module => module.default && 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 && module.default.encryptAndUpload) ? module.default : null)[0].default; - window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default; - window.Store.Validators = window.mR.findModule('findLinks')[0]; - window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; - window.Store.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.LinkPreview = window.mR.findModule('getLinkPreview')[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]; - window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo; - - /* 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 && m[0]; - /* eslint-enable no-undef, no-cond-assign */ - - window.Store.PinnedMsgUtils = { - ...window.mR.findModule('PinInChatCollection')[0], - getPinInChatByChatId: window.mR.findModule('getPinInChatByChatId')[0].getPinInChatByChatId, - seekAndDestroyExpiredPins: window.mR.findModule('seekAndDestroyExpiredPins')[0].seekAndDestroyExpiredPins, - pinUnpinMsg: window.mR.findModule('sendPinInChatMsg')[0].sendPinInChatMsg - }; - window.Store.Settings = { - ...window.mR.findModule('ChatlistPanelState')[0], - setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname - }; - 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]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; - - const _isMDBackend = window.mR.findModule('isMDBackend'); - if(_isMDBackend && _isMDBackend[0] && _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; - } +exports.LoadUtils = () => { + window.WWebJS = {}; - /** - * Target options object description - * @typedef {Object} TargetOptions - * @property {string|number} module The name or a key of the target module to search - * @property {number} index The index value of the target module - * @property {string} function The function name to get from a module - */ + window.WWebJS.forwardMessage = async (chatId, msgId) => { + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + let chat = window.Store.Chat.get(chatId); - /** - * 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.module === 'string' - ? window.mR.findModule(target.module) - : window.mR.modules[target.module]; - const originalFunction = module[target.index][target.function]; - const modifiedFunction = (...args) => callback(originalFunction, ...args); - module[target.index][target.function] = modifiedFunction; + if (window.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) { + return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], false); + } else { + return chat.forwardMessages([msg]); + } }; - window.injectToFunction({ module: 'mediaTypeFromProtobuf', index: 0, function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); - - window.injectToFunction({ module: 'typeAttributeFromProtobuf', index: 0, function: '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) { @@ -187,7 +44,13 @@ exports.LoadUtils = () => { } let quotedMsgOptions = {}; if (options.quotedMessageId) { - let quotedMessage = window.Store.Msg.get(options.quotedMessageId); + let quotedMessage = await window.Store.Msg.getMessagesById([options.quotedMessageId]); + + if (quotedMessage['messages'].length != 1) { + throw new Error('Could not get the quoted message.'); + } + + quotedMessage = quotedMessage['messages'][0]; // TODO remove .canReply() once all clients are updated to >= v2.2241.6 const canReply = window.Store.ReplyUtils ? @@ -338,15 +201,23 @@ exports.LoadUtils = () => { delete listOptions.list.footer; } + const botOptions = {}; + if (options.invokedBotWid) { + botOptions.messageSecret = window.crypto.getRandomValues(new Uint8Array(32)); + botOptions.botMessageSecret = await window.Store.BotSecret.genBotMsgSecretFromMsgSecret(botOptions.messageSecret); + botOptions.invokedBotWid = window.Store.WidFactory.createWid(options.invokedBotWid); + botOptions.botPersonaId = window.Store.BotProfiles.BotProfileCollection.get(options.invokedBotWid).personaId; + delete options.invokedBotWid; + } + 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 && chat.id.isGroup() ? meUser : undefined, + participant: chat.id.isGroup() ? meUser : undefined, selfDir: 'out', }); @@ -376,8 +247,14 @@ exports.LoadUtils = () => { ...vcardOptions, ...buttonOptions, ...listOptions, + ...botOptions, ...extraOptions }; + + // Bot's won't reply if canonicalUrl is set (linking) + if (botOptions) { + delete message.canonicalUrl; + } await window.Store.SendMessage.addAndSendMsgToChat(chat, message); return window.Store.Msg.get(newMsgId._serialized); @@ -562,14 +439,13 @@ exports.LoadUtils = () => { return msg; }; - window.WWebJS.getPollVoteModel = (vote) => { + window.WWebJS.getPollVoteModel = async (vote) => { const _vote = vote.serialize(); - if (vote.parentMsgKey) { - const msg = window.Store.Msg.get(vote.parentMsgKey); - msg && (_vote.parentMessage = window.WWebJS.getMessageModel(msg)); - return _vote; - } - return null; + if (!vote.parentMsgKey) return null; + const msg = + window.Store.Msg.get(vote.parentMsgKey) || (await window.Store.Msg.getMessagesById([vote.parentMsgKey]))?.messages?.[0]; + msg && (_vote.parentMessage = window.WWebJS.getMessageModel(msg)); + return _vote; }; window.WWebJS.getChatModel = async chat => { @@ -587,7 +463,9 @@ exports.LoadUtils = () => { res.lastMessage = null; if (res.msgs && res.msgs.length) { - const lastMessage = chat.lastReceivedKey ? window.Store.Msg.get(chat.lastReceivedKey._serialized) : null; + const lastMessage = chat.lastReceivedKey + ? window.Store.Msg.get(chat.lastReceivedKey._serialized) || (await window.Store.Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0] + : null; if (lastMessage) { res.lastMessage = window.WWebJS.getMessageModel(lastMessage); } @@ -672,7 +550,7 @@ exports.LoadUtils = () => { 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); + const bizProfile = await window.Store.BusinessProfile.fetchBizProfile(wid); bizProfile.profileOptions && (contact.businessProfile = bizProfile); return window.WWebJS.getContactModel(contact); }; @@ -793,9 +671,8 @@ exports.LoadUtils = () => { }; window.WWebJS.sendChatstate = async (state, chatId) => { - if (window.Store.MDBackend) { - chatId = window.Store.WidFactory.createWid(chatId); - } + chatId = window.Store.WidFactory.createWid(chatId); + switch (state) { case 'typing': await window.Store.ChatState.sendChatStateComposing(chatId); @@ -1043,7 +920,7 @@ exports.LoadUtils = () => { let response; let result = []; - await window.Store.GroupMetadata.queryAndUpdate(groupWid); + await window.Store.GroupQueryAndUpdate(groupWid); if (!requesterIds?.length) { membershipRequests = group.groupMetadata.membershipApprovalRequests._models.map(({ id }) => id); @@ -1131,10 +1008,21 @@ exports.LoadUtils = () => { }; window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => { - const message = window.Store.Msg.get(msgId); + const message = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; if (!message) return false; const response = await window.Store.PinnedMsgUtils.pinUnpinMsg(message, action, duration); - if (response.messageSendResult === 'OK') return true; - return false; + return response.messageSendResult === 'OK'; + }; + + window.WWebJS.getStatusModel = status => { + let res = status.serialize(); + delete res._msgs; + res.msgs = status._msgs.map(msg => window.WWebJS.getMessageModel(msg)); + return res; + }; + + window.WWebJS.getAllStatuses = () => { + const statuses = window.Store.Status.getModelsArray(); + return statuses.map(status => window.WWebJS.getStatusModel(status)); }; }; diff --git a/src/util/InterfaceController.js b/src/util/InterfaceController.js index 2c7e1cab0a..1f38931f3d 100644 --- a/src/util/InterfaceController.js +++ b/src/util/InterfaceController.js @@ -15,9 +15,9 @@ class InterfaceController { */ async openChatWindow(chatId) { await this.pupPage.evaluate(async chatId => { - let chatWid = window.Store.WidFactory.createWid(chatId); - let chat = await window.Store.Chat.find(chatWid); - await window.Store.Cmd.openChatAt(chat); + const chatWid = window.Store.WidFactory.createWid(chatId); + const chat = window.Store.Chat.get(chatWid) || await window.Store.Chat.find(chatWid); + await window.Store.Cmd.openChatBottom(chat); }, chatId); } @@ -49,7 +49,7 @@ class InterfaceController { */ async openChatWindowAt(msgId) { await this.pupPage.evaluate(async msgId => { - let msg = await window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; 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); @@ -62,7 +62,7 @@ class InterfaceController { */ async openMessageDrawer(msgId) { await this.pupPage.evaluate(async msgId => { - let msg = await window.Store.Msg.get(msgId); + const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; await window.Store.Cmd.msgInfoDrawer(msg); }, msgId); } diff --git a/src/util/Puppeteer.js b/src/util/Puppeteer.js new file mode 100644 index 0000000000..f6f2e43733 --- /dev/null +++ b/src/util/Puppeteer.js @@ -0,0 +1,23 @@ +/** + * Expose a function to the page if it does not exist + * + * NOTE: + * Rewrite it to 'upsertFunction' after updating Puppeteer to 20.6 or higher + * using page.removeExposedFunction + * https://pptr.dev/api/puppeteer.page.removeExposedFunction + * + * @param {import(puppeteer).Page} page + * @param {string} name + * @param {Function} fn + */ +async function exposeFunctionIfAbsent(page, name, fn) { + const exist = await page.evaluate((name) => { + return !!window[name]; + }, name); + if (exist) { + return; + } + await page.exposeFunction(name, fn); +} + +module.exports = {exposeFunctionIfAbsent}; diff --git a/src/webCache/LocalWebCache.js b/src/webCache/LocalWebCache.js index a377f559b5..9c429683f8 100644 --- a/src/webCache/LocalWebCache.js +++ b/src/webCache/LocalWebCache.js @@ -29,15 +29,12 @@ class LocalWebCache extends WebCache { } } - 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; - + async persist(indexHtml, version) { + // version = (version+'').replace(/[^0-9.]/g,''); 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 +module.exports = LocalWebCache; diff --git a/tools/version-checker/.version b/tools/version-checker/.version index 8986cbf954..374994e807 100644 --- a/tools/version-checker/.version +++ b/tools/version-checker/.version @@ -1 +1 @@ -2.2346.52 \ No newline at end of file +2.3000.1016590837 \ No newline at end of file