From 01c45047d2f93806c6288b70c4d0f35bfb82f52c Mon Sep 17 00:00:00 2001 From: alechkos <93551621+alechkos@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:51:12 +0300 Subject: [PATCH] docs: generate docs for the latest release (#3279) * docs: generate docs for the latest release * docs: mark version v1.26.0 * docs: mark WWeb version v2.3000.1016590837 --- README.md | 2 +- docs/Base.html | 6 +- docs/BaseAuthStrategy.html | 6 +- docs/BusinessContact.html | 6 +- docs/Buttons.html | 6 +- docs/Call.html | 6 +- docs/Chat.html | 6 +- docs/Client.html | 432 +++++- docs/Client.js.html | 1317 ++++++++++-------- docs/ClientInfo.html | 6 +- docs/Contact.html | 6 +- docs/GroupChat.html | 50 +- docs/GroupNotification.html | 6 +- docs/InterfaceController.html | 6 +- docs/Label.html | 6 +- docs/List.html | 6 +- docs/LocalAuth.html | 6 +- docs/LocalWebCache.html | 6 +- docs/Location.html | 6 +- docs/Message.html | 96 +- docs/MessageMedia.html | 6 +- docs/NoAuth.html | 6 +- docs/Order.html | 6 +- docs/Poll.html | 6 +- docs/PollVote.html | 119 ++ docs/PrivateChat.html | 6 +- docs/PrivateContact.html | 6 +- docs/Product.html | 6 +- docs/Reaction.html | 6 +- docs/RemoteAuth.html | 6 +- docs/RemoteWebCache.html | 6 +- docs/Util.html | 6 +- docs/WebCache.html | 6 +- docs/authStrategies_BaseAuthStrategy.js.html | 6 +- docs/authStrategies_LocalAuth.js.html | 11 +- docs/authStrategies_NoAuth.js.html | 6 +- docs/authStrategies_RemoteAuth.js.html | 6 +- docs/global.html | 390 +++++- docs/index.html | 320 ++++- docs/scripts/jsdoc-toc.js | 2 +- docs/structures_Base.js.html | 6 +- docs/structures_BusinessContact.js.html | 6 +- docs/structures_Buttons.js.html | 6 +- docs/structures_Call.js.html | 6 +- docs/structures_Chat.js.html | 6 +- docs/structures_ClientInfo.js.html | 6 +- docs/structures_Contact.js.html | 6 +- docs/structures_GroupChat.js.html | 43 +- docs/structures_GroupNotification.js.html | 6 +- docs/structures_Label.js.html | 6 +- docs/structures_List.js.html | 6 +- docs/structures_Location.js.html | 6 +- docs/structures_Message.js.html | 157 ++- docs/structures_MessageMedia.js.html | 6 +- docs/structures_Order.js.html | 6 +- docs/structures_Payment.js.html | 6 +- docs/structures_Poll.js.html | 6 +- docs/structures_PollVote.js.html | 115 ++ docs/structures_PrivateChat.js.html | 6 +- docs/structures_PrivateContact.js.html | 6 +- docs/structures_Product.js.html | 6 +- docs/structures_ProductMetadata.js.html | 6 +- docs/structures_Reaction.js.html | 6 +- docs/util_Constants.js.html | 10 +- docs/util_Injected_LegacyStore.js.html | 199 +++ docs/util_Injected_Store.js.html | 221 +++ docs/util_Injected_Utils.js.html | 1071 ++++++++++++++ docs/util_InterfaceController.js.html | 10 +- docs/util_Util.js.html | 6 +- docs/webCache_LocalWebCache.js.html | 16 +- docs/webCache_RemoteWebCache.js.html | 6 +- docs/webCache_WebCache.js.html | 6 +- src/util/Constants.js | 2 +- tools/version-checker/.version | 2 +- 74 files changed, 3955 insertions(+), 948 deletions(-) create mode 100644 docs/PollVote.html create mode 100644 docs/structures_PollVote.js.html create mode 100644 docs/util_Injected_LegacyStore.js.html create mode 100644 docs/util_Injected_Store.js.html create mode 100644 docs/util_Injected_Utils.js.html diff --git a/README.md b/README.md index 638f8d2..f5a0ebe 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 c6a0cbb..fdeaf0d 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 @@ diff --git a/docs/BaseAuthStrategy.html b/docs/BaseAuthStrategy.html index 80230ba..535a68c 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 @@ diff --git a/docs/BusinessContact.html b/docs/BusinessContact.html index 4c243a3..5229c4e 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 @@ diff --git a/docs/Buttons.html b/docs/Buttons.html index 3baa235..a11069a 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 @@ Returns: [{ buttonId:'customId',buttonText:{'displayText':&# diff --git a/docs/Call.html b/docs/Call.html index b7489d0..fb5c975 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 @@ diff --git a/docs/Chat.html b/docs/Chat.html index 706ea57..cdaa946 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 @@ diff --git a/docs/Client.html b/docs/Client.html index 46398b9..6a46128 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 @@
+
+
pupBrowser
+
+
+
+
+
pupPage
+
+
+
@@ -70,6 +80,9 @@
archiveChat()
+
attachEventListeners()
+
+
createGroup(title, participants, options)
@@ -100,13 +113,16 @@
getContactById(contactId)
+
getContactDeviceCount(contactId)
+
+
+
getContacts()
+
+
-
getContacts()
-
-
getCountryCode(number)
@@ -140,6 +156,9 @@
initialize()
+
inject()
+
+
isRegisteredUser(id)
@@ -149,10 +168,6 @@
markChatUnread(chatId)
-
-
-
-
muteChat(chatId, unmuteDate)
@@ -162,6 +177,13 @@
rejectGroupMembershipRequests(groupId, options)
+
+
+
+
+
requestPairingCode(phoneNumber, showNotification)
+
+
resetState()
@@ -180,6 +202,18 @@
sendSeen(chatId)
+
setAutoDownloadAudio(flag)
+
+
+
setAutoDownloadDocuments(flag)
+
+
+
setAutoDownloadPhotos(flag)
+
+
+
setAutoDownloadVideos(flag)
+
+
setDisplayName(displayName)
@@ -231,13 +265,13 @@
disconnected
+
group_admin_changed
+
+
-
group_admin_changed
-
-
group_join
@@ -259,13 +293,16 @@
message
+
message_ack
+
+
+
message_ciphertext
+
+
-
message_ack
-
-
message_create
@@ -287,6 +324,9 @@
ready
+
vote_update
+
+
@@ -494,7 +534,7 @@

 

-

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 @@
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 @@
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 @@
async
+

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 @@
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 @@
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 @@
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 @@
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 @@

reason

-

(WAState or "NAVIGATION")

+

(WAState or "LOGOUT")

 

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

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 @@

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 @@ diff --git a/docs/Client.js.html b/docs/Client.js.html index 0fe7276..ec7519e 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 @@ 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, 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 @@ const NoAuth = require('./authStrategies/NoAuth'); * @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 @@ const NoAuth = require('./authStrategies/NoAuth'); * @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 @@ const NoAuth = require('./authStrategies/NoAuth'); * @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 @@ 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); } + /** + * 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 @@ class Client extends EventEmitter { 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 @@ class Client extends EventEmitter { 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; - }`); + await this.inject(); - 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; + 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; - } - - const QR_CONTAINER = 'div[data-ref]'; - const QR_RETRY_BUTTON = 'div[data-ref] > span > button'; - let qrRetries = 0; - await page.exposeFunction('qrChanged', 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 page.evaluate(function (selectors) { - const qr_container = document.querySelector(selectors.QR_CONTAINER); - window.qrChanged(qr_container.dataset.ref); - - const obs = new MutationObserver((muts) => { - muts.forEach(mut => { - // Listens to qr token change - if (mut.type === 'attributes' &amp;&amp; mut.attributeName === 'data-ref') { - window.qrChanged(mut.target.dataset.ref); - } - // 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' &amp;&amp; - error.message &amp;&amp; - error.message.match(/Target closed/) - ) { - // something has called .destroy() while waiting return; } - throw error; - } + 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('onChangeMessageTypeEvent', (msg) => { + + 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. + */ + this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); + } + + }); + + await this.pupPage.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'; + + if (isParticipant || isContact) { + /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */ + const message = new Message(this, msg); + + const newId = isParticipant ? msg.recipients[0] : msg.to; + const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId); + + /** + * Emitted when a contact or a group participant changes their phone number. + * @event Client#contact_changed + * @param {Message} message Message with more information about the event. + * @param {String} oldId The user's id (an old one) who changed their phone number + * and who triggered the notification. + * @param {String} newId The user's new id after the change. + * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number. + */ + this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact); + } + }); + + await this.pupPage.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 this.pupPage.exposeFunction('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 + */ + 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) => { + + 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 + */ + this.emit(Events.MEDIA_UPLOADED, message); + }); + + 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); + + const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT]; + + if (this.options.takeoverOnConflict) { + ACCEPTED_STATES.push(WAState.CONFLICT); + + if (state === WAState.CONFLICT) { + setTimeout(() => { + this.pupPage.evaluate(() => window.Store.AppState.takeover()); + }, this.options.takeoverTimeoutMs); + } + } + + 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(); + } + }); + + await this.pupPage.exposeFunction('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 + */ + this.emit(Events.BATTERY_CHANGED, { battery, plugged }); + }); + + 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); + }); + + 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 + */ + + this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction)); + } + }); + + await this.pupPage.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 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 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); + }); + + 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 page.evaluate(() => { + await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => { + const _vote = new PollVote(this, vote); /** - * 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 + * Emitted when some poll option is selected or deselected, + * shows a user's current selected option(s) on the poll + * @event Client#vote_update */ - 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 - ); - }; + this.emit(Events.VOTE_UPDATE, _vote); }); - await page.evaluate(ExposeStore, moduleRaid.toString()); - const authEventPayload = await this.authStrategy.getAuthEventPayload(); - - /** - * Emitted when authentication is successful - * @event Client#authenticated - */ - this.emit(Events.AUTHENTICATED, authEventPayload); - - // Check window.Store Injection - await page.waitForFunction('window.Store != undefined'); - - await page.evaluate(async () => { - // safely unregister service workers - const registrations = await navigator.serviceWorker.getRegistrations(); - for (let registration of registrations) { - registration.unregister(); - } - }); - - //Load util functions (serializers, helper functions) - await page.evaluate(LoadUtils); - - // 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 - */ - 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); - } - 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 page.exposeFunction('onChangeMessageTypeEvent', (msg) => { - - 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. - */ - this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); - } - - }); - - 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'; - - if (isParticipant || isContact) { - /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */ - const message = new Message(this, msg); - - const newId = isParticipant ? msg.recipients[0] : msg.to; - const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId); - - /** - * Emitted when a contact or a group participant changes their phone number. - * @event Client#contact_changed - * @param {Message} message Message with more information about the event. - * @param {String} oldId The user's id (an old one) who changed their phone number - * and who triggered the notification. - * @param {String} newId The user's new id after the change. - * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number. - */ - this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact); - } - }); - - await page.exposeFunction('onRemoveMessageEvent', (msg) => { - - 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) => { - - 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 page.exposeFunction('onChatUnreadCountEvent', async (data) =>{ - const chat = await this.getChatById(data.id); - - /** - * Emitted when the chat unread count changes - */ - this.emit(Events.UNREAD_COUNT, chat); - }); - - await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => { - - const message = new Message(this, msg); - - /** - * 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) => { - - /** - * 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]; - - if (this.options.takeoverOnConflict) { - ACCEPTED_STATES.push(WAState.CONFLICT); - - if (state === WAState.CONFLICT) { - setTimeout(() => { - this.pupPage.evaluate(() => window.Store.AppState.takeover()); - }, this.options.takeoverTimeoutMs); - } - } - - if (!ACCEPTED_STATES.includes(state)) { - /** - * Emitted when the client has been disconnected - * @event Client#disconnected - * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect - */ - await this.authStrategy.disconnect(); - this.emit(Events.DISCONNECTED, state); - this.destroy(); - } - }); - - await page.exposeFunction('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 - */ - this.emit(Events.BATTERY_CHANGED, { battery, plugged }); - }); - - 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 page.exposeFunction('onReaction', (reactions) => { - for (const reaction of reactions) { - /** - * Emitted when a reaction is sent, received, updated or removed - * @event Client#message_reaction - * @param {object} reaction - * @param {object} reaction.id - Reaction id - * @param {number} reaction.orphan - Orphan - * @param {?string} reaction.orphanReason - Orphan reason - * @param {number} reaction.timestamp - Timestamp - * @param {string} reaction.reaction - Reaction - * @param {boolean} reaction.read - Read - * @param {object} reaction.msgId - Parent message id - * @param {string} reaction.senderId - Sender id - * @param {?number} reaction.ack - Ack - */ - - this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction)); - } - }); - - await page.exposeFunction('onRemoveChatEvent', async (chat) => { - const _chat = await this.getChatById(chat.id); - - /** - * Emitted when a chat is removed - * @event Client#chat_removed - * @param {Chat} chat - */ - this.emit(Events.CHAT_REMOVED, _chat); - }); - - await page.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => { - const _chat = await this.getChatById(chat.id); - - /** - * Emitted when a chat is archived/unarchived - * @event Client#chat_archived - * @param {Chat} chat - * @param {boolean} currState - * @param {boolean} prevState - */ - this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState); - }); - - await page.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => { - - if(msg.type === 'revoked'){ - return; - } - /** - * Emitted when messages are edited - * @event Client#message_edit - * @param {Message} message - * @param {string} newBody - * @param {string} prevBody - */ - this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); - }); - - await page.evaluate(() => { + 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 @@ class Client extends EventEmitter { 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 @@ 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; @@ -772,7 +814,8 @@ class Client extends EventEmitter { } 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 @@ class Client extends EventEmitter { */ 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 @@ class Client extends EventEmitter { 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 @@ class Client extends EventEmitter { * @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 @@ class Client extends EventEmitter { * @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 @@ class Client extends EventEmitter { 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 @@ class Client extends EventEmitter { 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 @@ 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 &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 @@ 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) { - // 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 @@ 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, '&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 @@ 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'; @@ -1405,7 +1471,7 @@ class Client extends EventEmitter { 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 @@ class Client extends EventEmitter { * @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 @@ class Client extends EventEmitter { 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 @@ module.exports = Client; diff --git a/docs/ClientInfo.html b/docs/ClientInfo.html index 015f210..df2c6aa 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 @@ diff --git a/docs/Contact.html b/docs/Contact.html index 5a9f658..fa564cb 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 @@ diff --git a/docs/GroupChat.html b/docs/GroupChat.html index 37e2024..f030c2f 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 @@
sendStateTyping()
+
setAddMembersAdminsOnly([adminsOnly])
+
+
setDescription(description)
@@ -930,6 +933,47 @@
Chat#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 @@
diff --git a/docs/GroupNotification.html b/docs/GroupNotification.html index a66ec93..b9698d9 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 @@
diff --git a/docs/InterfaceController.html b/docs/InterfaceController.html index 262c122..9fa2ab3 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 @@
diff --git a/docs/Label.html b/docs/Label.html index 8b3219a..e3109ec 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 @@
diff --git a/docs/List.html b/docs/List.html index 9135bdf..4a4d88c 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 @@ Returns: [{'title':'sectionTitle','rows':[{'r
diff --git a/docs/LocalAuth.html b/docs/LocalAuth.html index 81f524f..f33cae4 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 @@
diff --git a/docs/LocalWebCache.html b/docs/LocalWebCache.html index bd21a4d..7c8e972 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 @@
diff --git a/docs/Location.html b/docs/Location.html index 588d576..af24f45 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 @@
diff --git a/docs/Message.html b/docs/Message.html index 64420ec..063daef 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 @@
fromMe
+
groupMentions
+
+
hasMedia
@@ -103,13 +106,13 @@
links
+
location
+
+
-
location
-
-
mediaKey
@@ -164,11 +167,14 @@
getChat()
+
getContact()
+
+
-
getReactions()
+
pin(duration)
react(reaction)
@@ -205,6 +214,9 @@
star()
+
unpin()
+
+
unstar()
@@ -269,6 +281,11 @@

Indicates if the message was sent by the current user

+

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 @@

mentionedIds -  Array of string

+  Array of Mention

Indicates the mentions in the message body.

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

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 @@
async
+

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 @@
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 @@
diff --git a/docs/MessageMedia.html b/docs/MessageMedia.html index 4b58ccb..80d2104 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 @@
diff --git a/docs/NoAuth.html b/docs/NoAuth.html index 36f7a61..913f14c 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 @@
diff --git a/docs/Order.html b/docs/Order.html index ac40b6c..a27a893 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 @@
diff --git a/docs/Poll.html b/docs/Poll.html index ba996d6..56eab4d 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 @@
diff --git a/docs/PollVote.html b/docs/PollVote.html new file mode 100644 index 0000000..2516a2a --- /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 d708426..ed4e95c 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 @@
diff --git a/docs/PrivateContact.html b/docs/PrivateContact.html index a1c8aeb..a4a76a7 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 @@
diff --git a/docs/Product.html b/docs/Product.html index 510c8aa..40dfb48 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 @@
diff --git a/docs/Reaction.html b/docs/Reaction.html index 510a722..6848cbf 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 @@
diff --git a/docs/RemoteAuth.html b/docs/RemoteAuth.html index d55ea49..2a51db1 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 @@
diff --git a/docs/RemoteWebCache.html b/docs/RemoteWebCache.html index b139248..0ac8c87 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 @@
diff --git a/docs/Util.html b/docs/Util.html index 9208ce1..2ee6d46 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 @@
diff --git a/docs/WebCache.html b/docs/WebCache.html index 3acfa73..deb2656 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 @@
diff --git a/docs/authStrategies_BaseAuthStrategy.js.html b/docs/authStrategies_BaseAuthStrategy.js.html index b978fd0..7e3087d 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 @@ module.exports = BaseAuthStrategy;
diff --git a/docs/authStrategies_LocalAuth.js.html b/docs/authStrategies_LocalAuth.js.html index ab86187..1aba149 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 @@ 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); + }); } } @@ -92,7 +95,7 @@ module.exports = LocalAuth;
diff --git a/docs/authStrategies_NoAuth.js.html b/docs/authStrategies_NoAuth.js.html index 60e4ecb..1870304 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 @@ module.exports = NoAuth;
diff --git a/docs/authStrategies_RemoteAuth.js.html b/docs/authStrategies_RemoteAuth.js.html index 09bd8b2..49f3cc6 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 @@ module.exports = RemoteAuth;
diff --git a/docs/global.html b/docs/global.html index ef80193..aaea4b3 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 @@
GroupMembershipRequest
+
GroupMembershipRequest
+
+
@@ -304,6 +319,19 @@ + + +

MESSAGE_CIPHERTEXT

+ + +

 

+ + +

 

+ + + +

MESSAGE_CREATE

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

VOTE_UPDATE

+ + +

 

+ + +

 

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

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

Mention +  Object

+
+

Properties

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

server

+
+

string

+
+

 

+
+
+

user

+
+

string

+
+

 

+
+
+

_serialized

+
+

string

+
+

 

+
+
+
+
+

MessageInfo  Object

Message Info

@@ -2808,16 +3054,30 @@ -

mentions

+

groupMentions

-

Array of Contact

+

Array of GroupMention

Yes

-

Contacts that are being mentioned in the message

+

An array of object that handle group mentions

+ + + + +

mentions

+ + +

Array of string

+ + +

Yes

+ + +

User IDs to mention in the message

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

Defaults to true.

+ + +

invokedBotWid

+ + +

string

+ + +

Yes

+ + +

Bot Wid when doing a bot mention like @Meta AI

+ +

stickerAuthor

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

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 @@ -

moduleId

+

module

(string or number)

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

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 @@
diff --git a/docs/index.html b/docs/index.html index e29c2b6..b07a22a 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 @@ client.on('message', msg => {
 
 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 @@ client.initialize(); - + @@ -116,11 +155,11 @@ client.initialize(); - + - + @@ -163,6 +202,10 @@ client.initialize(); + + + + @@ -186,11 +229,27 @@ client.initialize(); + + + + + + + + + + + + + + + +
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 @@ client.initialize();
  • 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 @@ limitations under the License.

+
+ Client#attachEventListeners() +
+
+
Client#createGroup(title, participants, options)
@@ -867,11 +931,6 @@ limitations under the License.

- -
- -
-
Client#event:message
@@ -882,6 +941,16 @@ limitations under the License.

+
+ Client#event:message_ciphertext +
+
+
+
+
+ +
+
Client#event:message_create
@@ -917,6 +986,11 @@ limitations under the License.

+
+ Client#event:vote_update +
+
+
Client#getBlockedContacts()
@@ -952,6 +1026,11 @@ limitations under the License.

+
+ Client#getContactDeviceCount(contactId) +
+
+
Client#getContacts()
@@ -992,11 +1071,6 @@ limitations under the License.

-
-
- -
-
Client#getProfilePicUrl(contactId)
@@ -1017,11 +1091,21 @@ limitations under the License.

+
+
+ +
+
Client#initialize()
+
+ Client#inject() +
+
+
Client#isRegisteredUser(id)
@@ -1047,11 +1131,26 @@ limitations under the License.

+
+ Client#pupBrowser +
+
+
+
+ Client#pupPage +
+
+
Client#rejectGroupMembershipRequests(groupId, options)
+
+ Client#requestPairingCode(phoneNumber, showNotification) +
+
+
Client#resetState()
@@ -1082,6 +1181,26 @@ limitations under the License.

+
+ Client#setAutoDownloadAudio(flag) +
+
+
+
+ Client#setAutoDownloadDocuments(flag) +
+
+
+
+ Client#setAutoDownloadPhotos(flag) +
+
+
+
+ Client#setAutoDownloadVideos(flag) +
+
+
Client#setDisplayName(displayName)
@@ -1358,16 +1477,16 @@ limitations under the License.

-
-
- - + +
+
Events.GROUP_MEMBERSHIP_REQUEST
@@ -1398,6 +1517,11 @@ limitations under the License.

+
+ Events.MESSAGE_CIPHERTEXT +
+
+
Events.MESSAGE_CREATE
@@ -1408,16 +1532,16 @@ limitations under the License.

-
-
- - + +
+
Events.MESSAGE_RECEIVED
@@ -1458,6 +1582,11 @@ limitations under the License.

+
+ Events.VOTE_UPDATE +
+
+
@@ -1680,6 +1809,11 @@ limitations under the License.

+
+ GroupChat#setAddMembersAdminsOnly([adminsOnly]) +
+
+
GroupChat#setDescription(description)
@@ -2009,33 +2143,6 @@ limitations under the License.

-
-
-

LegacySessionAuth

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

List

@@ -2295,6 +2402,11 @@ limitations under the License.

+
+ Message#getGroupMentions() +
+
+
@@ -2330,6 +2442,11 @@ limitations under the License.

+
+ Message#groupMentions +
+
+
Message#hasMedia
@@ -2410,6 +2527,11 @@ limitations under the License.

+
+ Message#pin(duration) +
+
+
Message#rawData
@@ -2455,6 +2577,11 @@ limitations under the License.

+
+ Message#unpin() +
+
+
Message#unstar()
@@ -2960,6 +3087,53 @@ limitations under the License.

+
+
+

PollVote

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

PrivateChat

@@ -3734,7 +3908,7 @@ limitations under the License.

diff --git a/docs/scripts/jsdoc-toc.js b/docs/scripts/jsdoc-toc.js index 80c357f..a21d2ac 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 dc4ee1f..f5a09a2 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 @@ module.exports = Base;
diff --git a/docs/structures_BusinessContact.js.html b/docs/structures_BusinessContact.js.html index 54b5345..5d34a72 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 @@ module.exports = BusinessContact;
diff --git a/docs/structures_Buttons.js.html b/docs/structures_Buttons.js.html index 0ccc184..6264599 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 @@ module.exports = Buttons;
diff --git a/docs/structures_Call.js.html b/docs/structures_Call.js.html index 089363c..c76c3ff 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 @@ module.exports = Call;
diff --git a/docs/structures_Chat.js.html b/docs/structures_Chat.js.html index c9dec6e..22bea7b 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 @@ module.exports = Chat;
diff --git a/docs/structures_ClientInfo.js.html b/docs/structures_ClientInfo.js.html index 4c19f3c..2d7f831 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 @@ module.exports = ClientInfo;
diff --git a/docs/structures_Contact.js.html b/docs/structures_Contact.js.html index 06a1e0a..58aaaf7 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 @@ module.exports = Contact;
diff --git a/docs/structures_GroupChat.js.html b/docs/structures_GroupChat.js.html index 319c776..03b4e35 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 @@ 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; @@ -183,7 +183,7 @@ class GroupChat extends Chat { 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 @@ class GroupChat extends Chat { 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 @@ 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; } /** @@ -485,7 +514,7 @@ module.exports = GroupChat;
diff --git a/docs/structures_GroupNotification.js.html b/docs/structures_GroupNotification.js.html index 9e71139..2ebf6d2 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 @@ module.exports = GroupNotification;
diff --git a/docs/structures_Label.js.html b/docs/structures_Label.js.html index a0df02a..74aee29 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 @@ module.exports = Label;
diff --git a/docs/structures_List.js.html b/docs/structures_List.js.html index a6e5e51..722e99f 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 @@ module.exports = List;
diff --git a/docs/structures_Location.js.html b/docs/structures_Location.js.html index c56cb78..217e826 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 @@ module.exports = Location;
diff --git a/docs/structures_Message.js.html b/docs/structures_Message.js.html index 6b1b4bb..5013f81 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 @@ const Location = require('./Location'); 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 @@ 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' &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; /** - * Indicates the mentions in the message body. - * @type {Array&lt;string>} + * @typedef {Object} Mention + * @property {string} server + * @property {string} user + * @property {string} _serialized */ - this.mentionedIds = []; - if (data.mentionedJidList) { - this.mentionedIds = data.mentionedJidList; - } + /** + * Indicates the mentions in the message body. + * @type {Mention[]} + */ + 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 @@ class Message extends Base { 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 @@ class Message extends Base { * @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 @@ class Message extends Base { 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 @@ 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); @@ -419,9 +440,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); } @@ -444,10 +466,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); } @@ -461,9 +480,9 @@ class Message extends Base { } 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 @@ 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 &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 @@ 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); @@ -544,8 +566,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); @@ -553,6 +574,27 @@ class Message extends Base { }, 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 @@ class Message extends Base { */ /** - * 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 @@ 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); @@ -654,12 +701,20 @@ class Message extends Base { * @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 @@ 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 = (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 @@ module.exports = Message;
diff --git a/docs/structures_MessageMedia.js.html b/docs/structures_MessageMedia.js.html index 60a1248..4b334eb 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 @@ module.exports = MessageMedia;
diff --git a/docs/structures_Order.js.html b/docs/structures_Order.js.html index cb091f3..4a89915 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 @@ module.exports = Order;
diff --git a/docs/structures_Payment.js.html b/docs/structures_Payment.js.html index f4598b1..a0cd91e 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 @@ module.exports = Payment;
diff --git a/docs/structures_Poll.js.html b/docs/structures_Poll.js.html index da5c481..7e05c31 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 @@ module.exports = Poll;
diff --git a/docs/structures_PollVote.js.html b/docs/structures_PollVote.js.html new file mode 100644 index 0000000..63e4c20 --- /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 e337bd5..e8889b9 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 @@ module.exports = PrivateChat;
diff --git a/docs/structures_PrivateContact.js.html b/docs/structures_PrivateContact.js.html index 5017b0f..2df6fae 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 @@ module.exports = PrivateContact;
diff --git a/docs/structures_Product.js.html b/docs/structures_Product.js.html index a5e898f..ab12f12 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 @@ module.exports = Product;
diff --git a/docs/structures_ProductMetadata.js.html b/docs/structures_ProductMetadata.js.html index c0c38ad..7b09d69 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 @@ module.exports = ProductMetadata;
diff --git a/docs/structures_Reaction.js.html b/docs/structures_Reaction.js.html index a2ac667..80ff22a 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 @@ module.exports = Reaction;
diff --git a/docs/util_Constants.js.html b/docs/util_Constants.js.html index 596f819..c045d04 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 @@ exports.Events = { 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 @@ exports.Events = { 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 @@ exports.MessageAck = {
diff --git a/docs/util_Injected_LegacyStore.js.html b/docs/util_Injected_LegacyStore.js.html new file mode 100644 index 0000000..82d7127 --- /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 0000000..b96ac29 --- /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 0000000..2cf922c --- /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 9c0196c..ec6a5c2 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 @@ 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); @@ -93,7 +93,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); } @@ -166,7 +166,7 @@ module.exports = InterfaceController;
diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index d8ce40d..bda87a9 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 @@ module.exports = Util;
diff --git a/docs/webCache_LocalWebCache.js.html b/docs/webCache_LocalWebCache.js.html index ccdb09d..d820adf 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 @@ 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; +module.exports = LocalWebCache; +
@@ -81,7 +79,7 @@ module.exports = LocalWebCache;
diff --git a/docs/webCache_RemoteWebCache.js.html b/docs/webCache_RemoteWebCache.js.html index 75a643e..88d4f7b 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 @@ module.exports = RemoteWebCache;
diff --git a/docs/webCache_WebCache.js.html b/docs/webCache_WebCache.js.html index a19a40e..7f33579 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 @@ module.exports = { diff --git a/src/util/Constants.js b/src/util/Constants.js index e8c5fe3..9f33448 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/tools/version-checker/.version b/tools/version-checker/.version index 8986cbf..374994e 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