diff --git a/docs/Chat.html b/docs/Chat.html index 8fd2d47a174..d5a0469ab2c 100644 --- a/docs/Chat.html +++ b/docs/Chat.html @@ -48,13 +48,16 @@

Properties

isGroup
-
isMuted
+
isLocked
+
isMuted
+
+
isReadOnly
@@ -64,13 +67,13 @@

Properties

muteExpiration
-
name
-
-
+
name
+
+
pinned
@@ -193,6 +196,11 @@

isGroup

+

isLocked +  boolean

+

Indicates if the Chat is locked

+
+

isMuted  boolean

Indicates if the chat is muted or not

@@ -636,7 +644,7 @@

unpin diff --git a/docs/Client.html b/docs/Client.html index a1c22115599..cccfba6c7dd 100644 --- a/docs/Client.html +++ b/docs/Client.html @@ -23,7 +23,7 @@
-
message_ciphertext
-
-
message_create
@@ -497,7 +506,7 @@

Parameters

 

-

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

+

Determines how to save and restore sessions. If not set, NoAuth will be used.

@@ -593,36 +602,6 @@

Parameters

-

restartOnAuthFail

- - -

 

- - -

 

- - -

@deprecated This option should be set directly on the LegacySessionAuth.

- - - - - -

session

- - -

 

- - -

 

- - -

@deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.

- - - - -

takeoverOnConflict

@@ -1540,6 +1519,38 @@

Property

+ + + + +
+ +
async

cancelPairingCode()

Cancels an active pairing code session and returns to QR code mode

+ + + + + +
+ + + + + + + + + + + + + + + + + + + @@ -4996,6 +5007,86 @@

Parameters

+ + + +
+ +
async

sendReaction(messageId, reaction) → Promise

Send an emoji reaction to a specific message

+ + +
+

Parameters

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

messageId

+
+

string

+
+

 

+
+

Id of the message to add the reaction.

+
+

reaction

+
+

string

+
+

 

+
+

Emoji to react with. Send an empty string to remove the reaction.

+
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + +
Returns
+

Promise 

@@ -7526,7 +7617,69 @@

Parameters

-

message_ciphertext

Emitted when messages are edited

+

message_ciphertext

Emitted when a message is received as ciphertext (not yet decrypted)

+ + +
+

Parameter

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

message

+
+

Message

+
+

 

+
+
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +

message_ciphertext_failed

Emitted when a ciphertext message failed to decrypt after recovery attempt

@@ -8237,7 +8390,7 @@

vote_update

diff --git a/docs/Client.js.html b/docs/Client.js.html index f3ee3438108..81c138208b9 100644 --- a/docs/Client.js.html +++ b/docs/Client.js.html @@ -10,68 +10,84 @@ - - - + +
+
+
+ +
+ +
+
'use strict';
 
 const EventEmitter = require('events');
 const puppeteer = require('puppeteer');
-const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');
 
 const Util = require('./util/Util');
 const InterfaceController = require('./util/InterfaceController');
-const { WhatsWebURL, DefaultOptions, Events, WAState, MessageTypes } = require('./util/Constants');
+const {
+    WhatsWebURL,
+    DefaultOptions,
+    Events,
+    WAState,
+    MessageTypes,
+} = require('./util/Constants');
 const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore');
-const { ExposeStore } = require('./util/Injected/Store');
-const { ExposeLegacyAuthStore } = require('./util/Injected/AuthStore/LegacyAuthStore');
-const { ExposeLegacyStore } = require('./util/Injected/LegacyStore');
 const { LoadUtils } = require('./util/Injected/Utils');
 const ChatFactory = require('./factories/ChatFactory');
 const ContactFactory = require('./factories/ContactFactory');
 const WebCacheFactory = require('./webCache/WebCacheFactory');
-const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction, Broadcast, ScheduledEvent } = require('./structures');
+const {
+    ClientInfo,
+    Message,
+    MessageMedia,
+    Contact,
+    Location,
+    Poll,
+    PollVote,
+    GroupNotification,
+    Label,
+    Call,
+    Buttons,
+    List,
+    Reaction,
+    Broadcast,
+    ScheduledEvent,
+} = require('./structures');
 const NoAuth = require('./authStrategies/NoAuth');
-const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
+const { exposeFunctionIfAbsent } = require('./util/Puppeteer');
 
 /**
  * Starting point for interacting with the WhatsApp Web API
  * @extends {EventEmitter}
  * @param {object} options - Client options
- * @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used.
+ * @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. If not set, NoAuth will be used.
  * @param {string} options.webVersion - The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved.
  * @param {object} options.webVersionCache - Determines how to retrieve the WhatsApp Web version. Defaults to a local cache (LocalWebCache) that falls back to latest if the requested version is not found.
  * @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
  * @param {function} options.evalOnNewDoc - function to eval on new doc
  * @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
  * @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up
- * @param {string} options.restartOnAuthFail  - @deprecated This option should be set directly on the LegacySessionAuth.
- * @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly. 
  * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
  * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
  * @param {string} options.userAgent - User agent to use in puppeteer
- * @param {string} options.ffmpegPath - Ffmpeg path to use when formatting 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 {string} options.deviceName - Sets the device name of a current linked device., i.e.: 'TEST'.
  * @param {string} options.browserName - Sets the browser name of a current linked device, i.e.: 'Firefox'.
  * @param {object} options.proxyAuthentication - Proxy Authentication object.
- * 
+ *
  * @fires Client#qr
  * @fires Client#authenticated
  * @fires Client#auth_failure
@@ -99,8 +115,8 @@ 

Source: Client.js

super(); this.options = Util.mergeDefault(DefaultOptions, options); - - if(!this.options.authStrategy) { + + if (!this.options.authStrategy) { this.authStrategy = new NoAuth(); } else { this.authStrategy = this.options.authStrategy; @@ -127,53 +143,77 @@

Source: Client.js

* Private function */ async inject() { - if(this.options.authTimeoutMs === undefined || this.options.authTimeoutMs==0){ + if ( + this.options.authTimeoutMs === undefined || + this.options.authTimeoutMs == 0 + ) { this.options.authTimeoutMs = 30000; } let start = Date.now(); let timeout = this.options.authTimeoutMs; let res = false; - while(start > (Date.now() - timeout)){ - res = await this.pupPage.evaluate('window.Debug?.VERSION != undefined'); - if(res){break;} - await new Promise(r => setTimeout(r, 200)); + while (start > Date.now() - timeout) { + res = await this.pupPage.evaluate( + 'window.Debug?.VERSION != undefined', + ); + if (res) { + break; + } + await new Promise((r) => setTimeout(r, 200)); } - if(!res){ + if (!res) { throw 'auth timeout'; - } - await this.setDeviceName(this.options.deviceName, this.options.browserName); + } + await this.setDeviceName( + this.options.deviceName, + this.options.browserName, + ); const pairWithPhoneNumber = this.options.pairWithPhoneNumber; 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()); - } + await this.pupPage.evaluate(ExposeAuthStore); const needAuthentication = await this.pupPage.evaluate(async () => { - let state = window.AuthStore.AppState.state; + let state = window.require('WAWebSocketModel').Socket.state; - if (state === 'OPENING' || state === 'UNLAUNCHED' || state === 'PAIRING') { + if ( + state === 'OPENING' || + state === 'UNLAUNCHED' || + state === 'PAIRING' + ) { // wait till state changes - await new Promise(r => { - window.AuthStore.AppState.on('change:state', function waitTillInit(_AppState, state) { - if (state !== 'OPENING' && state !== 'UNLAUNCHED' && state !== 'PAIRING') { - window.AuthStore.AppState.off('change:state', waitTillInit); - r(); - } - }); - }); + await new Promise((r) => { + window + .require('WAWebSocketModel') + .Socket.on( + 'change:state', + function waitTillInit(_AppState, state) { + if ( + state !== 'OPENING' && + state !== 'UNLAUNCHED' && + state !== 'PAIRING' + ) { + window + .require('WAWebSocketModel') + .Socket.off( + 'change:state', + waitTillInit, + ); + r(); + } + }, + ); + }); } - state = window.AuthStore.AppState.state; + state = window.require('WAWebSocketModel').Socket.state; return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE'; }); if (needAuthentication) { - const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); + const { failed, failureEventPayload, restart } = + await this.authStrategy.onAuthenticationNeeded(); - if(failed) { + if (failed) { /** * Emitted when there has been an error while trying to restore an existing session * @event Client#auth_failure @@ -190,138 +230,224 @@

Source: Client.js

// Register qr/code events if (pairWithPhoneNumber.phoneNumber) { - await exposeFunctionIfAbsent(this.pupPage, 'onCodeReceivedEvent', async (code) => { - /** - * Emitted when a pairing code is received - * @event Client#code - * @param {string} code Code - * @returns {string} Code that was just received - */ - this.emit(Events.CODE_RECEIVED, code); - return code; - }); - this.requestPairingCode(pairWithPhoneNumber.phoneNumber, pairWithPhoneNumber.showNotification, pairWithPhoneNumber.intervalMs); + await exposeFunctionIfAbsent( + this.pupPage, + 'onCodeReceivedEvent', + async (code) => { + /** + * Emitted when a pairing code is received + * @event Client#code + * @param {string} code Code + * @returns {string} Code that was just received + */ + this.emit(Events.CODE_RECEIVED, code); + return code; + }, + ); + this.requestPairingCode( + pairWithPhoneNumber.phoneNumber, + pairWithPhoneNumber.showNotification, + pairWithPhoneNumber.intervalMs, + ); } else { let qrRetries = 0; - await exposeFunctionIfAbsent(this.pupPage, '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 exposeFunctionIfAbsent( + this.pupPage, + '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; - + 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 platform = + window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; + const getQR = (ref) => + ref + + ',' + + staticKeyB64 + + ',' + + identityKeyB64 + + ',' + + window + .require('WAWebUserPrefsMultiDevice') + .getADVSecretKey() + + ',' + + platform; window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr - window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes + window.AuthStore.Conn.on('change:ref', (_, ref) => { + window.onQRChangedEvent(getQR(ref)); + }); // future QR changes }); } } - await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => { - if (state == 'UNPAIRED_IDLE' && !pairWithPhoneNumber.phoneNumber) { - // refresh qr code - window.Store.Cmd.refreshQR(); - } - }); - - await exposeFunctionIfAbsent(this.pupPage, 'onAppStateHasSyncedEvent', async () => { - const authEventPayload = await this.authStrategy.getAuthEventPayload(); - /** + await exposeFunctionIfAbsent( + this.pupPage, + 'onAuthAppStateChangedEvent', + async (state) => { + if ( + state == 'UNPAIRED_IDLE' && + !pairWithPhoneNumber.phoneNumber + ) { + // refresh qr code + window.require('WAWebCmd').Cmd.refreshQR(); + } + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onAppStateHasSyncedEvent', + async () => { + const authEventPayload = + await this.authStrategy.getAuthEventPayload(); + /** * Emitted when authentication is successful * @event Client#authenticated */ - this.emit(Events.AUTHENTICATED, authEventPayload); + this.emit(Events.AUTHENTICATED, authEventPayload); - const injected = await this.pupPage.evaluate(async () => { - return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined'; - }); + const injected = await this.pupPage.evaluate(async () => { + return typeof window.WWebJS !== 'undefined'; + }); - if (!injected) { - if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) { - const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; - const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions); - - await webCache.persist(this.currentIndexHtml, version); - } + if (!injected) { + if ( + this.options.webVersionCache.type === 'local' && + this.currentIndexHtml + ) { + const { type: webCacheType, ...webCacheOptions } = + this.options.webVersionCache; + const webCache = WebCacheFactory.createWebCache( + webCacheType, + webCacheOptions, + ); + + await webCache.persist(this.currentIndexHtml, version); + } - if (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); - } - let start = Date.now(); - let res = false; - while(start > (Date.now() - 30000)){ - // Check window.Store Injection - res = await this.pupPage.evaluate('window.Store != undefined'); - if(res){break;} - await new Promise(r => setTimeout(r, 200)); - } - if(!res){ - throw 'ready timeout'; - } - - /** + //Load util functions (serializers, helper functions) + await this.pupPage.evaluate(LoadUtils); + + let start = Date.now(); + let res = false; + while (start > Date.now() - 30000) { + // Check window.WWebJS Injection + res = await this.pupPage.evaluate( + 'window.WWebJS != undefined', + ); + if (res) { + break; + } + await new Promise((r) => setTimeout(r, 200)); + } + if (!res) { + throw 'ready timeout'; + } + + /** * Current connection information * @type {ClientInfo} */ - this.info = new ClientInfo(this, await this.pupPage.evaluate(() => { - return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMaybeMePnUser() || window.Store.User.getMaybeMeLidUser() }; - })); - - this.interface = new InterfaceController(this); + this.info = new ClientInfo( + this, + await this.pupPage.evaluate(() => { + return { + ...window + .require('WAWebConnModel') + .Conn.serialize(), + wid: + window + .require('WAWebUserPrefsMeUser') + .getMaybeMePnUser() || + window + .require('WAWebUserPrefsMeUser') + .getMaybeMeLidUser(), + }; + }), + ); - //Load util functions (serializers, helper functions) - await this.pupPage.evaluate(LoadUtils); + this.interface = new InterfaceController(this); - await this.attachEventListeners(); - } - /** + await this.attachEventListeners(); + } + /** * Emitted when the client has initialized and is ready to receive messages. * @event Client#ready */ - this.emit(Events.READY); - this.authStrategy.afterAuthReady(); - }); + this.emit(Events.READY); + this.authStrategy.afterAuthReady(); + }, + ); let lastPercent = null; - await exposeFunctionIfAbsent(this.pupPage, 'onOfflineProgressUpdateEvent', async (percent) => { - if (lastPercent !== percent) { - lastPercent = percent; - this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now - } - }); - await exposeFunctionIfAbsent(this.pupPage, 'onLogoutEvent', async () => { - this.lastLoggedOut = true; - await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _); - }); + await exposeFunctionIfAbsent( + this.pupPage, + 'onOfflineProgressUpdateEvent', + async (percent) => { + if (lastPercent !== percent) { + lastPercent = percent; + this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now + } + }, + ); + await exposeFunctionIfAbsent( + this.pupPage, + 'onLogoutEvent', + async () => { + this.lastLoggedOut = true; + await this.pupPage + .waitForNavigation({ waitUntil: 'load', timeout: 5000 }) + .catch((_) => _); + }, + ); await this.pupPage.evaluate(() => { - window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); }); - window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); }); - window.AuthStore.Cmd.on('offline_progress_update', () => { - window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress()); + window + .require('WAWebSocketModel') + .Socket.on('change:state', (_AppState, state) => { + window.onAuthAppStateChangedEvent(state); + }); + window + .require('WAWebSocketModel') + .Socket.on('change:hasSynced', () => { + window.onAppStateHasSyncedEvent(); + }); + const Cmd = window.require('WAWebCmd').Cmd; + Cmd.on('offline_progress_update_from_bridge', () => { + window.onOfflineProgressUpdateEvent( + window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress(), + ); + }); + Cmd.on('logout', async () => { + await window.onLogoutEvent(); }); - window.AuthStore.Cmd.on('logout', async () => { + Cmd.on('logout_from_bridge', async () => { await window.onLogoutEvent(); }); }); @@ -331,12 +457,10 @@

Source: Client.js

* Sets up events and requirements, kicks off authentication request */ async initialize() { - - let - /** + let /** * @type {puppeteer.Browser} */ - browser, + browser, /** * @type {puppeteer.Page} */ @@ -348,25 +472,34 @@

Source: Client.js

await this.authStrategy.beforeBrowserInitialized(); const puppeteerOpts = this.options.puppeteer; - if (puppeteerOpts && (puppeteerOpts.browserWSEndpoint || puppeteerOpts.browserURL)) { + if ( + puppeteerOpts && + (puppeteerOpts.browserWSEndpoint || puppeteerOpts.browserURL) + ) { browser = await puppeteer.connect(puppeteerOpts); page = await browser.newPage(); } else { const browserArgs = [...(puppeteerOpts.args || [])]; - if(this.options.userAgent !== false && !browserArgs.find(arg => arg.includes('--user-agent'))) { + if ( + this.options.userAgent !== false && + !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}); + browser = await puppeteer.launch({ + ...puppeteerOpts, + args: browserArgs, + }); page = (await browser.pages())[0]; } if (this.options.proxyAuthentication !== undefined) { await page.authenticate(this.options.proxyAuthentication); } - if(this.options.userAgent !== false) { + if (this.options.userAgent !== false) { await page.setUserAgent(this.options.userAgent); } if (this.options.bypassCSP) await page.setBypassCSP(true); @@ -376,35 +509,21 @@

Source: Client.js

await this.authStrategy.afterBrowserInitialized(); await this.initWebVersionCache(); - + if (this.options.evalOnNewDoc !== undefined) { await page.evaluateOnNewDocument(this.options.evalOnNewDoc); } - - // 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/' + referer: 'https://whatsapp.com/', }); await this.inject(); this.pupPage.on('framenavigated', async (frame) => { - if(frame.url().includes('post_logout=1') || this.lastLoggedOut) { + if (frame.url().includes('post_logout=1') || this.lastLoggedOut) { this.emit(Events.DISCONNECTED, 'LOGOUT'); await this.authStrategy.logout(); await this.authStrategy.beforeBrowserInitialized(); @@ -422,28 +541,70 @@

Source: Client.js

* @param {number} [intervalMs = 180000] - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) * @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH" */ - async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) { - return await this.pupPage.evaluate(async (phoneNumber, showNotification, intervalMs) => { - const getCode = async () => { - while (!window.AuthStore.PairingCodeLinkUtils) { - await new Promise(resolve => setTimeout(resolve, 250)); + async requestPairingCode( + phoneNumber, + showNotification = true, + intervalMs = 180000, + ) { + await exposeFunctionIfAbsent( + this.pupPage, + 'onCodeReceivedEvent', + async (code) => { + this.emit(Events.CODE_RECEIVED, code); + return code; + }, + ); + return await this.pupPage.evaluate( + async (phoneNumber, showNotification, intervalMs) => { + const getCode = async () => { + while (!window.AuthStore.PairingCodeLinkUtils) { + await new Promise((resolve) => + setTimeout(resolve, 250), + ); + } + window.AuthStore.PairingCodeLinkUtils.setPairingType( + 'ALT_DEVICE_LINKING', + ); + await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); + return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow( + phoneNumber, + showNotification, + ); + }; + if (window.codeInterval) { + clearInterval(window.codeInterval); // remove existing interval } - window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); - await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); - return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); - }; + window.codeInterval = setInterval(async () => { + const state = + window.require('WAWebSocketModel').Socket.state; + if (state != 'UNPAIRED' && state != 'UNPAIRED_IDLE') { + clearInterval(window.codeInterval); + return; + } + window.onCodeReceivedEvent(await getCode()); + }, intervalMs); + return window.onCodeReceivedEvent(await getCode()); + }, + phoneNumber, + showNotification, + intervalMs, + ); + } + + /** + * Cancels an active pairing code session and returns to QR code mode + */ + async cancelPairingCode() { + await this.pupPage.evaluate(async () => { if (window.codeInterval) { - clearInterval(window.codeInterval); // remove existing interval + clearInterval(window.codeInterval); + window.codeInterval = undefined; } - window.codeInterval = setInterval(async () => { - if (window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE') { - clearInterval(window.codeInterval); - return; - } - window.onCodeReceivedEvent(await getCode()); - }, intervalMs); - return window.onCodeReceivedEvent(await getCode()); - }, phoneNumber, showNotification, intervalMs); + window.require('WAWebLaunchSocketUtils').refreshQR(); + await window + .require('WAWebAltDeviceLinkingApi') + .initializeQRLinking(); + }); } /** @@ -452,32 +613,45 @@

Source: Client.js

* @property {boolean} reinject is this a reinject? */ async attachEventListeners() { - await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageEvent', msg => { - if (msg.type === 'gp2') { - const notification = new GroupNotification(this, msg); - if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) { - /** + await exposeFunctionIfAbsent( + this.pupPage, + 'onAddMessageEvent', + (msg) => { + if (msg.type === 'gp2') { + const notification = new GroupNotification(this, msg); + if ( + ['add', 'invite', 'linked_group_join'].includes( + msg.subtype, + ) + ) { + /** * Emitted when a user joins the chat via invite link or is added by an admin. * @event Client#group_join * @param {GroupNotification} notification GroupNotification with more information about the action */ - this.emit(Events.GROUP_JOIN, notification); - } else if (msg.subtype === 'remove' || msg.subtype === 'leave') { - /** + 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') { - /** + 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') { - /** + 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 @@ -486,89 +660,106 @@

Source: Client.js

* @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 { - /** + this.emit( + Events.GROUP_MEMBERSHIP_REQUEST, + notification, + ); + } else { + /** * Emitted when group settings are updated, such as subject, description or picture. * @event Client#group_update * @param {GroupNotification} notification GroupNotification with more information about the action */ - this.emit(Events.GROUP_UPDATE, notification); + this.emit(Events.GROUP_UPDATE, notification); + } + return; } - return; - } - const message = new Message(this, msg); + 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); + this.emit(Events.MESSAGE_CREATE, message); - if (msg.id.fromMe) return; + 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); - }); + this.emit(Events.MESSAGE_RECEIVED, message); + }, + ); let last_message; - await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageTypeEvent', (msg) => { - - if (msg.type === 'revoked') { - const message = new Message(this, msg); - let revoked_msg; - if (last_message && msg.id.id === last_message.id.id) { - revoked_msg = new Message(this, last_message); - - if (message.protocolMessageKey) - revoked_msg.id = { ...message.protocolMessageKey }; - } + await exposeFunctionIfAbsent( + this.pupPage, + 'onChangeMessageTypeEvent', + (msg) => { + if (msg.type === 'revoked') { + const message = new Message(this, msg); + let revoked_msg; + if (last_message && msg.id.id === last_message.id.id) { + revoked_msg = new Message(this, last_message); + + if (message.protocolMessageKey) + revoked_msg.id = { ...message.protocolMessageKey }; + } - /** + /** * 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. + * @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 exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageEvent', (msg) => { - - if (msg.type !== 'revoked') { - last_message = msg; - } + this.emit( + Events.MESSAGE_REVOKED_EVERYONE, + message, + revoked_msg, + ); + } + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onChangeMessageEvent', + (msg) => { + if (msg.type !== 'revoked') { + last_message = msg; + } - /** + /** * The event notification that is received when one of * the group participants changes their phone number. */ - const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify'; + const isParticipant = + msg.type === 'gp2' && msg.subtype === 'modify'; - /** + /** * The event notification that is received when one of * the contacts changes their phone number. */ - const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number'; + const isContact = + msg.type === 'notification_template' && + msg.subtype === 'change_number'; - if (isParticipant || isContact) { - /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */ - const message = new Message(this, msg); + 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); + 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. @@ -577,98 +768,132 @@

Source: Client.js

* @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 exposeFunctionIfAbsent(this.pupPage, 'onRemoveMessageEvent', (msg) => { + this.emit( + Events.CONTACT_CHANGED, + message, + oldId, + newId, + isContact, + ); + } + }, + ); - if (!msg.isNewMsg) return; + await exposeFunctionIfAbsent( + this.pupPage, + 'onRemoveMessageEvent', + (msg) => { + if (!msg.isNewMsg) return; - const message = new Message(this, msg); + 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 exposeFunctionIfAbsent(this.pupPage, 'onMessageAckEvent', (msg, ack) => { - - const message = new Message(this, msg); + this.emit(Events.MESSAGE_REVOKED_ME, message); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onMessageAckEvent', + (msg, ack) => { + const message = new Message(this, msg); - /** + /** * Emitted when an ack event occurrs on message type. * @event Client#message_ack * @param {Message} message The message that was affected * @param {MessageAck} ack The new ACK value */ - this.emit(Events.MESSAGE_ACK, message, ack); + this.emit(Events.MESSAGE_ACK, message, ack); + }, + ); - }); + await exposeFunctionIfAbsent( + this.pupPage, + 'onChatUnreadCountEvent', + async (data) => { + const chat = await this.getChatById(data.id); - await exposeFunctionIfAbsent(this.pupPage, 'onChatUnreadCountEvent', async (data) =>{ - const chat = await this.getChatById(data.id); - - /** + /** * Emitted when the chat unread count changes */ - this.emit(Events.UNREAD_COUNT, chat); - }); - - await exposeFunctionIfAbsent(this.pupPage, 'onMessageMediaUploadedEvent', (msg) => { - - const message = new Message(this, msg); + this.emit(Events.UNREAD_COUNT, chat); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onMessageMediaUploadedEvent', + (msg) => { + const message = new Message(this, msg); - /** + /** * Emitted when media has been uploaded for a message sent by the client. * @event Client#media_uploaded * @param {Message} message The message with media that was uploaded */ - this.emit(Events.MEDIA_UPLOADED, message); - }); - - await exposeFunctionIfAbsent(this.pupPage, 'onAppStateChangedEvent', async (state) => { - /** + this.emit(Events.MEDIA_UPLOADED, message); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + '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); + 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 + .require('WAWebSocketModel') + .Socket.takeover(), + ); + }, this.options.takeoverTimeoutMs); + } } - } - if (!ACCEPTED_STATES.includes(state)) { - /** + 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.authStrategy.disconnect(); + this.emit(Events.DISCONNECTED, state); + this.destroy(); + } + }, + ); - await exposeFunctionIfAbsent(this.pupPage, 'onBatteryStateChangedEvent', (state) => { - const { battery, plugged } = state; + await exposeFunctionIfAbsent( + this.pupPage, + 'onBatteryStateChangedEvent', + (state) => { + const { battery, plugged } = state; - if (battery === undefined) return; + 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 @@ -676,30 +901,34 @@

Source: Client.js

* @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) * @deprecated */ - this.emit(Events.BATTERY_CHANGED, { battery, plugged }); - }); + this.emit(Events.BATTERY_CHANGED, { battery, plugged }); + }, + ); await exposeFunctionIfAbsent(this.pupPage, 'onIncomingCall', (call) => { /** - * Emitted when a call is received - * @event Client#incoming_call - * @param {object} call - * @param {number} call.id - Call id - * @param {string} call.peerJid - Who called - * @param {boolean} call.isVideo - if is video - * @param {boolean} call.isGroup - if is group - * @param {boolean} call.canHandleLocally - if we can handle in waweb - * @param {boolean} call.outgoing - if is outgoing - * @param {boolean} call.webClientShouldHandle - If Waweb should handle - * @param {object} call.participants - Participants - */ + * Emitted when a call is received + * @event Client#incoming_call + * @param {object} call + * @param {number} call.id - Call id + * @param {string} call.peerJid - Who called + * @param {boolean} call.isVideo - if is video + * @param {boolean} call.isGroup - if is group + * @param {boolean} call.canHandleLocally - if we can handle in waweb + * @param {boolean} call.outgoing - if is outgoing + * @param {boolean} call.webClientShouldHandle - If Waweb should handle + * @param {object} call.participants - Participants + */ const cll = new Call(this, call); this.emit(Events.INCOMING_CALL, cll); }); - await exposeFunctionIfAbsent(this.pupPage, 'onReaction', (reactions) => { - for (const reaction of reactions) { - /** + await exposeFunctionIfAbsent( + this.pupPage, + 'onReaction', + (reactions) => { + for (const reaction of reactions) { + /** * Emitted when a reaction is sent, received, updated or removed * @event Client#message_reaction * @param {object} reaction @@ -714,188 +943,340 @@

Source: Client.js

* @param {?number} reaction.ack - Ack */ - this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction)); - } - }); + this.emit( + Events.MESSAGE_REACTION, + new Reaction(this, reaction), + ); + } + }, + ); - await exposeFunctionIfAbsent(this.pupPage, 'onRemoveChatEvent', async (chat) => { - const _chat = await this.getChatById(chat.id); + await exposeFunctionIfAbsent( + this.pupPage, + 'onRemoveChatEvent', + async (chat) => { + const _chat = await this.getChatById(chat.id); - /** + /** * Emitted when a chat is removed * @event Client#chat_removed * @param {Chat} chat */ - this.emit(Events.CHAT_REMOVED, _chat); - }); - - await exposeFunctionIfAbsent(this.pupPage, 'onArchiveChatEvent', async (chat, currState, prevState) => { - const _chat = await this.getChatById(chat.id); - - /** + this.emit(Events.CHAT_REMOVED, _chat); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onArchiveChatEvent', + async (chat, currState, prevState) => { + const _chat = await this.getChatById(chat.id); + + /** * Emitted when a chat is archived/unarchived * @event Client#chat_archived * @param {Chat} chat * @param {boolean} currState * @param {boolean} prevState */ - this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState); - }); - - await exposeFunctionIfAbsent(this.pupPage, 'onEditMessageEvent', (msg, newBody, prevBody) => { - - if(msg.type === 'revoked'){ - return; - } - /** + this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onEditMessageEvent', + (msg, newBody, prevBody) => { + if (msg.type === 'revoked') { + return; + } + /** * Emitted when messages are edited * @event Client#message_edit * @param {Message} message * @param {string} newBody * @param {string} prevBody */ - this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); - }); - - await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageCiphertextEvent', msg => { - - /** - * Emitted when messages are edited + this.emit( + Events.MESSAGE_EDIT, + new Message(this, msg), + newBody, + prevBody, + ); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onAddMessageCiphertextEvent', + (msg) => { + /** + * Emitted when a message is received as ciphertext (not yet decrypted) * @event Client#message_ciphertext * @param {Message} message */ - this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); - }); - - await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (votes) => { - for (const vote of votes) { + this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onCiphertextFailedEvent', + (msg) => { /** - * Emitted when some poll option is selected or deselected, - * shows a user's current selected option(s) on the poll - * @event Client#vote_update + * Emitted when a ciphertext message failed to decrypt after recovery attempt + * @event Client#message_ciphertext_failed + * @param {Message} message */ - this.emit(Events.VOTE_UPDATE, new PollVote(this, vote)); - } - }); + this.emit( + Events.MESSAGE_CIPHERTEXT_FAILED, + new Message(this, msg), + ); + }, + ); + + await exposeFunctionIfAbsent( + this.pupPage, + 'onPollVoteEvent', + (votes) => { + for (const vote of votes) { + /** + * Emitted when some poll option is selected or deselected, + * shows a user's current selected option(s) on the poll + * @event Client#vote_update + */ + this.emit(Events.VOTE_UPDATE, new PollVote(this, vote)); + } + }, + ); 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 && !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 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); }); - const callCollection = (window.Store && window.Store.Call) || (window.Store && window.Store.WAWebCallCollection); - if (callCollection && typeof callCollection.on === 'function') { - callCollection.on('add', (call) => { window.onIncomingCall(call); }); + const { Msg, Chat, WAWebCallCollection } = + window.require('WAWebCollections'); + const AppState = window.require('WAWebSocketModel').Socket; + + // Enable placeholder message resend (recovery for ciphertext messages) + const gatingUtils = window.require('WAWebSyncGatingUtils'); + gatingUtils.isPlaceholderMessageResendEnabled = () => true; + + Msg.on('change', (msg) => { + window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); + }); + Msg.on('change:type', (msg) => { + window.onChangeMessageTypeEvent( + window.WWebJS.getMessageModel(msg), + ); + }); + Msg.on('change:ack', (msg, ack) => { + window.onMessageAckEvent( + window.WWebJS.getMessageModel(msg), + ack, + ); + }); + Msg.on('change:isUnsentMedia', (msg, unsent) => { + if (msg.id.fromMe && !unsent) + window.onMessageMediaUploadedEvent( + window.WWebJS.getMessageModel(msg), + ); + }); + Msg.on('remove', (msg) => { + if (msg.isNewMsg) + window.onRemoveMessageEvent( + window.WWebJS.getMessageModel(msg), + ); + }); + Msg.on('change:body change:caption', (msg, newBody, prevBody) => { + window.onEditMessageEvent( + window.WWebJS.getMessageModel(msg), + newBody, + prevBody, + ); + }); + AppState.on('change:state', (_AppState, state) => { + window.onAppStateChangedEvent(state); + }); + window + .require('WAWebConnModel') + .Conn.on('change:battery', (state) => { + window.onBatteryStateChangedEvent(state); + }); + if ( + WAWebCallCollection && + typeof WAWebCallCollection.on === 'function' + ) { + WAWebCallCollection.on('add', (call) => { + window.onIncomingCall(call); + }); } - window.Store.Chat.on('remove', async (chat) => { window.onRemoveChatEvent(await window.WWebJS.getChatModel(chat)); }); - window.Store.Chat.on('change:archive', async (chat, currState, prevState) => { window.onArchiveChatEvent(await window.WWebJS.getChatModel(chat), currState, prevState); }); - window.Store.Msg.on('add', (msg) => { - if (msg.isNewMsg) { - if(msg.type === 'ciphertext') { - // 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)); - } + Chat.on('remove', async (chat) => { + window.onRemoveChatEvent( + await window.WWebJS.getChatModel(chat), + ); + }); + Chat.on('change:archive', async (chat, currState, prevState) => { + window.onArchiveChatEvent( + await window.WWebJS.getChatModel(chat), + currState, + prevState, + ); + }); + const pendingResend = new Set(); + let resendFlush = null; + + function requestResend(msg) { + pendingResend.add(msg); + if (resendFlush) return; + resendFlush = setTimeout(() => { + resendFlush = null; + const msgs = [...pendingResend]; + pendingResend.clear(); + if (msgs.length === 0) return; + window + .require( + 'WAWebNonMessageDataRequestPlaceholderMessageResendUtils', + ) + .handlePlaceholderMsgsSeen(msgs, true); + }, 5000); + } + + Msg.on('add', (msg) => { + if (!msg.isNewMsg) return; + + if (msg.type !== 'ciphertext') { + window.onAddMessageEvent( + window.WWebJS.getMessageModel(msg), + ); + return; } + + window.onAddMessageCiphertextEvent( + window.WWebJS.getMessageModel(msg), + ); + + if (msg.subtype && msg.subtype.endsWith('_unavailable_fanout')) + return; + + requestResend(msg); + + const failTimer = setTimeout(() => { + if (msg.type !== 'ciphertext') return; + window.onCiphertextFailedEvent( + window.WWebJS.getMessageModel(msg), + ); + }, 15000); + + msg.once('change:type', (_msg) => { + clearTimeout(failTimer); + pendingResend.delete(_msg); + if (_msg.type === 'revoked') return; + window.onAddMessageEvent( + window.WWebJS.getMessageModel(_msg), + ); + }); + }); + Chat.on('change:unreadCount', (chat) => { + window.onChatUnreadCountEvent(chat); }); - window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);}); - - 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); - - const pollVoteModule = window.Store.AddonPollVoteTable; - const ogPollVoteMethod = pollVoteModule.bulkUpsert; - - pollVoteModule.bulkUpsert = (async (...args) => { - const votes = await Promise.all(args[0].map(async vote => { - const msgKey = vote.id; - const parentMsgKey = vote.pollUpdateParentKey; - const timestamp = vote.t / 1000; - const sender = vote.author ?? vote.from; - const senderUserJid = sender._serialized; - - let parentMessage = window.Store.Msg.get(parentMsgKey._serialized); - if (!parentMessage) { - const fetched = await window.Store.Msg.getMessagesById([parentMsgKey._serialized]); - parentMessage = fetched?.messages?.[0] || null; - } - return { - ...vote, - msgKey, - sender, - parentMsgKey, - senderUserJid, - timestamp, - parentMessage - }; - })); + window.WWebJS.injectToFunction( + { + module: 'WAWebAddonReactionTableMode', + function: 'reactionTableMode.bulkUpsert', + }, + (module, origFunction, ...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 origFunction.apply(module, args); + }, + ); + + window.WWebJS.injectToFunction( + { + module: 'WAWebAddonPollVoteTableMode', + function: 'pollVoteTableMode.bulkUpsert', + }, + async (module, origFunction, ...args) => { + const votes = await Promise.all( + args[0].map(async (vote) => { + const msgKey = vote.id; + const parentMsgKey = vote.pollUpdateParentKey; + const timestamp = vote.t / 1000; + const sender = vote.author ?? vote.from; + const senderUserJid = sender._serialized; + + let parentMessage = Msg.get( + parentMsgKey._serialized, + ); + if (!parentMessage) { + const fetched = await Msg.getMessagesById([ + parentMsgKey._serialized, + ]); + parentMessage = fetched?.messages?.[0] || null; + } + + return { + ...vote, + msgKey, + sender, + parentMsgKey, + senderUserJid, + timestamp, + parentMessage, + }; + }), + ); window.onPollVoteEvent(votes); - return ogPollVoteMethod.apply(pollVoteModule, args); - }).bind(pollVoteModule); - } else { - const module = window.Store.createOrUpdateReactionsModule; - const ogMethod = module.createOrUpdateReactions; - module.createOrUpdateReactions = ((...args) => { - window.onReaction(args[0].map(reaction => { - const msgKey = window.Store.MsgKey.fromString(reaction.msgKey); - const parentMsgKey = window.Store.MsgKey.fromString(reaction.parentMsgKey); - const timestamp = reaction.timestamp / 1000; - - return {...reaction, msgKey, parentMsgKey, timestamp }; - })); - - return ogMethod(...args); - }).bind(module); - } + return origFunction.apply(module, args); + }, + ); }); - } + } async initWebVersionCache() { - const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; - const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions); + const { type: webCacheType, ...webCacheOptions } = + this.options.webVersionCache; + const webCache = WebCacheFactory.createWebCache( + webCacheType, + webCacheOptions, + ); const requestedVersion = this.options.webVersion; const versionContent = await webCache.resolve(requestedVersion); - if(versionContent) { + if (versionContent) { await this.pupPage.setRequestInterception(true); this.pupPage.on('request', async (req) => { - if(req.url() === WhatsWebURL) { + if (req.url() === WhatsWebURL) { req.respond({ status: 200, contentType: 'text/html', - body: versionContent - }); + body: versionContent, + }); } else { req.continue(); } }); } else { this.pupPage.on('response', async (res) => { - if(res.ok() && res.url() === WhatsWebURL) { + if (res.ok() && res.url() === WhatsWebURL) { const indexHtml = await res.text(); this.currentIndexHtml = indexHtml; } @@ -920,18 +1301,17 @@

Source: Client.js

*/ async logout() { await this.pupPage.evaluate(() => { - if (window.Store && window.Store.AppState && typeof window.Store.AppState.logout === 'function') { - return window.Store.AppState.logout(); - } + return window.require('WAWebSocketModel').Socket.logout(); }); await this.pupBrowser.close(); - + let maxDelay = 0; - while (this.pupBrowser.isConnected() && (maxDelay < 10)) { // waits a maximum of 1 second before calling the AuthStrategy - await new Promise(resolve => setTimeout(resolve, 100)); - maxDelay++; + while (this.pupBrowser.isConnected() && maxDelay < 10) { + // waits a maximum of 1 second before calling the AuthStrategy + await new Promise((resolve) => setTimeout(resolve, 100)); + maxDelay++; } - + await this.authStrategy.logout(); } @@ -946,23 +1326,28 @@

Source: Client.js

} async setDeviceName(deviceName, browserName) { - (deviceName || browserName) && await this.pupPage.evaluate((deviceName, browserName) => { - const func = window.require('WAWebMiscBrowserUtils').info; - window.require('WAWebMiscBrowserUtils').info = () => { - return { - ...func(), - ...(deviceName ? { os: deviceName } : {}), - ...(browserName ? { name: browserName } : {}) - }; - }; - }, deviceName, browserName); + (deviceName || browserName) && + (await this.pupPage.evaluate( + (deviceName, browserName) => { + const func = window.require('WAWebMiscBrowserUtils').info; + window.require('WAWebMiscBrowserUtils').info = () => { + return { + ...func(), + ...(deviceName ? { os: deviceName } : {}), + ...(browserName ? { name: browserName } : {}), + }; + }; + }, + deviceName, + browserName, + )); } /** * Mark as seen for the Chat * @param {string} chatId * @returns {Promise<boolean>} result - * + * */ async sendSeen(chatId) { return await this.pupPage.evaluate(async (chatId) => { @@ -1002,50 +1387,84 @@

Source: Client.js

* @property {MessageMedia} [media] - Media to be sent * @property {any} [extra] - Extra options */ - + /** * Send a message to a specific chatId * @param {string} chatId * @param {string|MessageMedia|Location|Poll|Contact|Array<Contact>|Buttons|List} content * @param {MessageSendOptions} [options] - Options used when sending the message - * + * * @returns {Promise<Message>} Message that was just sent */ async sendMessage(chatId, content, options = {}) { const isChannel = /@\w*newsletter\b/.test(chatId); const isStatus = /@\w*broadcast\b/.test(chatId); - if (isChannel && [ - options.sendMediaAsDocument, options.quotedMessageId, - options.parseVCards, options.isViewOnce, - content instanceof Location, content instanceof Contact, - content instanceof Buttons, content instanceof List, - Array.isArray(content) && content.length > 0 && content[0] instanceof Contact - ].includes(true)) { - console.warn('The message type is currently not supported for sending in channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.'); + if ( + isChannel && + [ + options.sendMediaAsDocument, + options.quotedMessageId, + options.parseVCards, + options.isViewOnce, + content instanceof Location, + content instanceof Contact, + content instanceof Buttons, + content instanceof List, + Array.isArray(content) && + content.length > 0 && + content[0] instanceof Contact, + ].includes(true) + ) { + console.warn( + 'The message type is currently not supported for sending in channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.', + ); return null; - - } else if (isStatus && [ - options.sendMediaAsDocument, options.quotedMessageId, - options.parseVCards, options.isViewOnce, options.sendMediaAsSticker, - content instanceof Location, content instanceof Contact, - content instanceof Poll, content instanceof Buttons, content instanceof List, - Array.isArray(content) && content.length > 0 && content[0] instanceof Contact - ].includes(true)) { - console.warn('The message type is currently not supported for sending in status broadcast,\nthe supported message types are: text, image, gif, audio and video.'); + } else if ( + isStatus && + [ + options.sendMediaAsDocument, + options.quotedMessageId, + options.parseVCards, + options.isViewOnce, + options.sendMediaAsSticker, + content instanceof Location, + content instanceof Contact, + content instanceof Poll, + content instanceof Buttons, + content instanceof List, + Array.isArray(content) && + content.length > 0 && + content[0] instanceof Contact, + ].includes(true) + ) { + console.warn( + 'The message type is currently not supported for sending in status broadcast,\nthe supported message types are: text, image, gif, audio and video.', + ); return null; } - + if (options.mentions) { - !Array.isArray(options.mentions) && (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); + !Array.isArray(options.mentions) && + (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 && !Array.isArray(options.groupMentions) && (options.groupMentions = [options.groupMentions]); - + options.groupMentions && + !Array.isArray(options.groupMentions) && + (options.groupMentions = [options.groupMentions]); + let internalOptions = { linkPreview: options.linkPreview === false ? undefined : true, sendAudioAsVoice: options.sendAudioAsVoice, @@ -1054,6 +1473,7 @@

Source: Client.js

sendMediaAsDocument: options.sendMediaAsDocument, sendMediaAsHd: options.sendMediaAsHd, caption: options.caption, + isCaptionByUser: options.caption ? true : false, quotedMessageId: options.quotedMessageId, parseVCards: options.parseVCards !== false, mentionedJidList: options.mentions || [], @@ -1061,20 +1481,18 @@

Source: Client.js

invokedBotWid: options.invokedBotWid, ignoreQuoteErrors: options.ignoreQuoteErrors !== false, waitUntilMsgSent: options.waitUntilMsgSent || false, - extraOptions: options.extra + extraOptions: options.extra, }; const sendSeen = options.sendSeen !== false; if (content instanceof MessageMedia) { internalOptions.media = content; - internalOptions.isViewOnce = options.isViewOnce, - content = ''; + ((internalOptions.isViewOnce = options.isViewOnce), (content = '')); } else if (options.media instanceof MessageMedia) { internalOptions.media = options.media; internalOptions.caption = content; - internalOptions.isViewOnce = options.isViewOnce, - content = ''; + ((internalOptions.isViewOnce = options.isViewOnce), (content = '')); } else if (content instanceof Location) { internalOptions.location = content; content = ''; @@ -1087,48 +1505,97 @@

Source: Client.js

} else if (content instanceof Contact) { internalOptions.contactCard = content.id._serialized; content = ''; - } else if (Array.isArray(content) && content.length > 0 && content[0] instanceof Contact) { - internalOptions.contactCardList = content.map(contact => contact.id._serialized); + } else if ( + Array.isArray(content) && + content.length > 0 && + content[0] instanceof Contact + ) { + internalOptions.contactCardList = content.map( + (contact) => contact.id._serialized, + ); content = ''; } else if (content instanceof Buttons) { - console.warn('Buttons are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.'); - if (content.type !== 'chat') { internalOptions.attachment = content.body; } + console.warn( + 'Buttons are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.', + ); + if (content.type !== 'chat') { + internalOptions.attachment = content.body; + } internalOptions.buttons = content; content = ''; } else if (content instanceof List) { - console.warn('Lists are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.'); + console.warn( + 'Lists are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.', + ); internalOptions.list = content; content = ''; } if (internalOptions.sendMediaAsSticker && internalOptions.media) { internalOptions.media = await Util.formatToWebpSticker( - internalOptions.media, { + internalOptions.media, + { name: options.stickerName, author: options.stickerAuthor, - categories: options.stickerCategories - }, this.pupPage + categories: options.stickerCategories, + }, + this.pupPage, ); } - const sentMsg = await this.pupPage.evaluate(async (chatId, content, options, sendSeen) => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); + const sentMsg = await this.pupPage.evaluate( + async (chatId, content, options, sendSeen) => { + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); - if (!chat) return null; + if (!chat) return null; - if (sendSeen) { - await window.WWebJS.sendSeen(chatId); - } - - const msg = await window.WWebJS.sendMessage(chat, content, options); - return msg - ? window.WWebJS.getMessageModel(msg) - : undefined; - }, chatId, content, internalOptions, sendSeen); + if (sendSeen) { + await window.WWebJS.sendSeen(chatId); + } - return sentMsg - ? new Message(this, sentMsg) - : undefined; + const msg = await window.WWebJS.sendMessage( + chat, + content, + options, + ); + return msg ? window.WWebJS.getMessageModel(msg) : undefined; + }, + chatId, + content, + internalOptions, + sendSeen, + ); + + return sentMsg ? new Message(this, sentMsg) : undefined; + } + + /** + * Send an emoji reaction to a specific message + * @param {string} messageId - Id of the message to add the reaction. + * @param {string} reaction - Emoji to react with. Send an empty string to remove the reaction. + * @return {Promise} + */ + async sendReaction(messageId, reaction) { + await this.pupPage.evaluate( + async (messageId, reaction) => { + if (!messageId) return null; + const msg = + window.require('WAWebCollections').Msg.get(messageId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([messageId]) + )?.messages?.[0]; + if (!msg) return null; + await window + .require('WAWebSendReactionMsgAction') + .sendReactionToMsg(msg, reaction); + }, + messageId, + reaction, + ); } /** @@ -1140,33 +1607,45 @@

Source: Client.js

* Sends a channel admin invitation to a user, allowing them to become an admin of the channel * @param {string} chatId The ID of a user to send the channel admin invitation to * @param {string} channelId The ID of a channel for which the invitation is being sent - * @param {SendChannelAdminInviteOptions} options + * @param {SendChannelAdminInviteOptions} options * @returns {Promise<boolean>} Returns true if an invitation was sent successfully, false otherwise */ async sendChannelAdminInvite(chatId, channelId, options = {}) { - const response = await this.pupPage.evaluate(async (chatId, channelId, options) => { - const channelWid = window.Store.WidFactory.createWid(channelId); - const chatWid = window.Store.WidFactory.createWid(chatId); - const chat = window.Store.Chat.get(chatWid) || (await window.Store.Chat.find(chatWid)); - - if (!chatWid.isUser()) { - return false; - } - - return await window.Store.SendChannelMessage.sendNewsletterAdminInviteMessage( - chat, - { - newsletterWid: channelWid, - invitee: chatWid, - inviteMessage: options.comment, - base64Thumb: await window.WWebJS.getProfilePicThumbToBase64(channelWid) + const response = await this.pupPage.evaluate( + async (chatId, channelId, options) => { + const { createWid } = window.require('WAWebWidFactory'); + const channelWid = createWid(channelId); + const chatWid = createWid(chatId); + const chat = + window.require('WAWebCollections').Chat.get(chatWid) || + (await window + .require('WAWebCollections') + .Chat.find(chatWid)); + + if (!chatWid.isUser()) { + return false; } - ); - }, chatId, channelId, options); + + return await window + .require('WAWebNewsletterSendMsgAction') + .sendNewsletterAdminInviteMessage(chat, { + newsletterWid: channelWid, + invitee: chatWid, + inviteMessage: options.comment, + base64Thumb: + await window.WWebJS.getProfilePicThumbToBase64( + channelWid, + ), + }); + }, + chatId, + channelId, + options, + ); return response.messageSendResult === 'OK'; } - + /** * Searches for messages * @param {string} query @@ -1177,12 +1656,22 @@

Source: Client.js

* @returns {Promise<Message[]>} */ async searchMessages(query, options = {}) { - const messages = await this.pupPage.evaluate(async (query, page, count, remote) => { - const { messages } = await window.Store.Msg.search(query, page, count, remote); - return messages.map(msg => window.WWebJS.getMessageModel(msg)); - }, query, options.page, options.limit, options.chatId); + const messages = await this.pupPage.evaluate( + async (query, page, count, remote) => { + const { messages } = await window + .require('WAWebCollections') + .Msg.search(query, page, count, remote); + return messages.map((msg) => + window.WWebJS.getMessageModel(msg), + ); + }, + query, + options.page, + options.limit, + options.chatId, + ); - return messages.map(msg => new Message(this, msg)); + return messages.map((msg) => new Message(this, msg)); } /** @@ -1194,7 +1683,7 @@

Source: Client.js

return await window.WWebJS.getChats(); }); - return chats.map(chat => ChatFactory.create(this, chat)); + return chats.map((chat) => ChatFactory.create(this, chat)); } /** @@ -1211,16 +1700,14 @@

Source: Client.js

/** * Gets chat or channel instance by ID - * @param {string} chatId + * @param {string} chatId * @returns {Promise<Chat|Channel>} */ async getChatById(chatId) { - const chat = await this.pupPage.evaluate(async chatId => { + const chat = await this.pupPage.evaluate(async (chatId) => { return await window.WWebJS.getChat(chatId); }, chatId); - return chat - ? ChatFactory.create(this, chat) - : undefined; + return chat ? ChatFactory.create(this, chat) : undefined; } /** @@ -1232,7 +1719,8 @@

Source: Client.js

const channel = await this.pupPage.evaluate(async (inviteCode) => { let channelMetadata; try { - channelMetadata = await window.WWebJS.getChannelMetadata(inviteCode); + channelMetadata = + await window.WWebJS.getChannelMetadata(inviteCode); } catch (err) { if (err.name === 'ServerStatusCodeError') return null; throw err; @@ -1240,9 +1728,7 @@

Source: Client.js

return await window.WWebJS.getChat(channelMetadata.id); }, inviteCode); - return channel - ? ChatFactory.create(this, channel) - : undefined; + return channel ? ChatFactory.create(this, channel) : undefined; } /** @@ -1254,7 +1740,7 @@

Source: Client.js

return window.WWebJS.getContacts(); }); - return contacts.map(contact => ContactFactory.create(this, contact)); + return contacts.map((contact) => ContactFactory.create(this, contact)); } /** @@ -1263,7 +1749,7 @@

Source: Client.js

* @returns {Promise<Contact>} */ async getContactById(contactId) { - let contact = await this.pupPage.evaluate(contactId => { + let contact = await this.pupPage.evaluate((contactId) => { return window.WWebJS.getContact(contactId); }, contactId); @@ -1276,20 +1762,24 @@

Source: Client.js

* @returns {Promise<Message>} */ async getMessageById(messageId) { - const msg = await this.pupPage.evaluate(async messageId => { - let msg = window.Store.Msg.get(messageId); - if(msg) return window.WWebJS.getMessageModel(msg); + const msg = await this.pupPage.evaluate(async (messageId) => { + let msg = window.require('WAWebCollections').Msg.get(messageId); + if (msg) return window.WWebJS.getMessageModel(msg); const params = messageId.split('_'); - if (params.length !== 3 && params.length !== 4) throw new Error('Invalid serialized message id specified'); + if (params.length !== 3 && params.length !== 4) + throw new Error('Invalid serialized message id specified'); + + let messagesObject = await window + .require('WAWebCollections') + .Msg.getMessagesById([messageId]); + if (messagesObject && messagesObject.messages.length) + msg = messagesObject.messages[0]; - let messagesObject = await window.Store.Msg.getMessagesById([messageId]); - if (messagesObject && messagesObject.messages.length) msg = messagesObject.messages[0]; - - if(msg) return window.WWebJS.getMessageModel(msg); + if (msg) return window.WWebJS.getMessageModel(msg); }, messageId); - if(msg) return new Message(this, msg); + if (msg) return new Message(this, msg); return null; } @@ -1300,24 +1790,37 @@

Source: Client.js

*/ async getPinnedMessages(chatId) { const pinnedMsgs = await this.pupPage.evaluate(async (chatId) => { - const chatWid = window.Store.WidFactory.createWid(chatId); - const chat = window.Store.Chat.get(chatWid) ?? await window.Store.Chat.find(chatWid); + const chatWid = window.require('WAWebWidFactory').createWid(chatId); + const chat = + window.require('WAWebCollections').Chat.get(chatWid) ?? + (await window.require('WAWebCollections').Chat.find(chatWid)); if (!chat) return []; - - const msgs = await window.Store.PinnedMsgUtils.getTable().equals(['chatId'], chatWid.toString()); + + const msgs = await window + .require('WAWebPinInChatSchema') + .getTable() + .equals(['chatId'], chatWid.toString()); const pinnedMsgs = ( await Promise.all( - msgs.filter(msg => msg.pinType == 1).map(async (msg) => { - const res = await window.Store.Msg.getMessagesById([msg.parentMsgKey]); - return res?.messages?.[0]; - }) + msgs + .filter((msg) => msg.pinType == 1) + .map(async (msg) => { + const res = await window + .require('WAWebCollections') + .Msg.getMessagesById([msg.parentMsgKey]); + return res?.messages?.[0]; + }), ) ).filter(Boolean); return !pinnedMsgs.length ? [] - : await Promise.all(pinnedMsgs.map((msg) => window.WWebJS.getMessageModel(msg))); + : await Promise.all( + pinnedMsgs.map((msg) => + window.WWebJS.getMessageModel(msg), + ), + ); }, chatId); return pinnedMsgs.map((msg) => new Message(this, msg)); @@ -1325,12 +1828,14 @@

Source: Client.js

/** * Returns an object with information about the invite code's group - * @param {string} inviteCode + * @param {string} inviteCode * @returns {Promise<object>} Invite information */ async getInviteInfo(inviteCode) { - return await this.pupPage.evaluate(inviteCode => { - return window.Store.GroupInvite.queryGroupInvite(inviteCode); + return await this.pupPage.evaluate((inviteCode) => { + return window + .require('WAWebGroupQueryJob') + .queryGroupInvite(inviteCode); }, inviteCode); } @@ -1340,8 +1845,10 @@

Source: Client.js

* @returns {Promise<string>} Id of the joined Chat */ async acceptInvite(inviteCode) { - const res = await this.pupPage.evaluate(async inviteCode => { - return await window.Store.GroupInvite.joinGroupViaInvite(inviteCode); + const res = await this.pupPage.evaluate(async (inviteCode) => { + return await window + .require('WAWebGroupInviteJob') + .joinGroupViaInvite(inviteCode); }, inviteCode); return res.gid._serialized; @@ -1355,7 +1862,9 @@

Source: Client.js

async acceptChannelAdminInvite(channelId) { return await this.pupPage.evaluate(async (channelId) => { try { - await window.Store.ChannelUtils.acceptNewsletterAdminInvite(channelId); + await window + .require('WAWebMexAcceptNewsletterAdminInviteJob') + .acceptNewsletterAdminInvite(channelId); return true; } catch (err) { if (err.name === 'ServerStatusCodeError') return false; @@ -1371,16 +1880,24 @@

Source: Client.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async revokeChannelAdminInvite(channelId, userId) { - return await this.pupPage.evaluate(async (channelId, userId) => { - try { - const userWid = window.Store.WidFactory.createWid(userId); - await window.Store.ChannelUtils.revokeNewsletterAdminInvite(channelId, userWid); - return true; - } catch (err) { - if (err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, channelId, userId); + return await this.pupPage.evaluate( + async (channelId, userId) => { + try { + const userWid = window + .require('WAWebWidFactory') + .createWid(userId); + await window + .require('WAWebMexRevokeNewsletterAdminInviteJob') + .revokeNewsletterAdminInvite(channelId, userWid); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + channelId, + userId, + ); } /** @@ -1390,16 +1907,24 @@

Source: Client.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async demoteChannelAdmin(channelId, userId) { - return await this.pupPage.evaluate(async (channelId, userId) => { - try { - const userWid = window.Store.WidFactory.createWid(userId); - await window.Store.ChannelUtils.demoteNewsletterAdmin(channelId, userWid); - return true; - } catch (err) { - if (err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, channelId, userId); + return await this.pupPage.evaluate( + async (channelId, userId) => { + try { + const userWid = window + .require('WAWebWidFactory') + .createWid(userId); + await window + .require('WAWebDemoteNewsletterAdminAction') + .demoteNewsletterAdmin(channelId, userWid); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + channelId, + userId, + ); } /** @@ -1408,12 +1933,20 @@

Source: Client.js

* @returns {Promise<Object>} */ async acceptGroupV4Invite(inviteInfo) { - if (!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object'; + if (!inviteInfo.inviteCode) + throw 'Invalid invite code, try passing the message.inviteV4 object'; if (inviteInfo.inviteCodeExp == 0) throw 'Expired invite code'; - return this.pupPage.evaluate(async inviteInfo => { + return this.pupPage.evaluate(async (inviteInfo) => { let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo; - let userWid = window.Store.WidFactory.createWid(fromId); - return await window.Store.GroupInviteV4.joinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, userWid); + let userWid = window.require('WAWebWidFactory').createWid(fromId); + return await window + .require('WAWebGroupInviteV4Job') + .joinGroupViaInviteV4( + inviteCode, + String(inviteCodeExp), + groupId, + userWid, + ); }, inviteInfo); } @@ -1422,35 +1955,39 @@

Source: Client.js

* @param {string} status New status message */ async setStatus(status) { - await this.pupPage.evaluate(async status => { - return await window.Store.StatusUtils.setMyStatus(status); + await this.pupPage.evaluate(async (status) => { + return await window + .require('WAWebContactStatusBridge') + .setMyStatus(status); }, status); } /** - * Sets the current user's display name. + * Sets the current user's display name. * This is the name shown to WhatsApp users that have not added you as a contact beside your number in groups and in your profile. * @param {string} displayName New display name * @returns {Promise<Boolean>} */ async setDisplayName(displayName) { - const couldSet = await this.pupPage.evaluate(async displayName => { - if(!window.Store.Conn.canSetMyPushname()) return false; - await window.Store.Settings.setPushname(displayName); + const couldSet = await this.pupPage.evaluate(async (displayName) => { + if (!window.require('WAWebConnModel').Conn.canSetMyPushname()) + return false; + await window + .require('WAWebSetPushnameConnAction') + .setPushname(displayName); return true; }, displayName); return couldSet; } - + /** * Gets the current connection state for the client - * @returns {WAState} + * @returns {WAState} */ async getState() { return await this.pupPage.evaluate(() => { - if(!window.Store) return null; - return window.Store.AppState.state; + return window.require('WAWebSocketModel').Socket.state ?? null; }); } @@ -1459,7 +1996,9 @@

Source: Client.js

*/ async sendPresenceAvailable() { return await this.pupPage.evaluate(() => { - return window.Store.PresenceUtils.sendPresenceAvailable(); + return window + .require('WAWebPresenceChatAction') + .sendPresenceAvailable(); }); } @@ -1468,7 +2007,9 @@

Source: Client.js

*/ async sendPresenceUnavailable() { return await this.pupPage.evaluate(() => { - return window.Store.PresenceUtils.sendPresenceUnavailable(); + return window + .require('WAWebPresenceChatAction') + .sendPresenceUnavailable(); }); } @@ -1477,9 +2018,11 @@

Source: Client.js

* @returns {boolean} */ async archiveChat(chatId) { - return await this.pupPage.evaluate(async chatId => { - let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - await window.Store.Cmd.archiveChat(chat, true); + return await this.pupPage.evaluate(async (chatId) => { + let chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + await window.require('WAWebCmd').Cmd.archiveChat(chat, true); return true; }, chatId); } @@ -1489,9 +2032,11 @@

Source: Client.js

* @returns {boolean} */ async unarchiveChat(chatId) { - return await this.pupPage.evaluate(async chatId => { - let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - await window.Store.Cmd.archiveChat(chat, false); + return await this.pupPage.evaluate(async (chatId) => { + let chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + await window.require('WAWebCmd').Cmd.archiveChat(chat, false); return false; }, chatId); } @@ -1501,20 +2046,24 @@

Source: Client.js

* @returns {Promise<boolean>} New pin state. Could be false if the max number of pinned chats was reached. */ async pinChat(chatId) { - return this.pupPage.evaluate(async chatId => { - let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); + return this.pupPage.evaluate(async (chatId) => { + let chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); if (chat.pin) { return true; } const MAX_PIN_COUNT = 3; - const chatModels = window.Store.Chat.getModelsArray(); + const chatModels = window + .require('WAWebCollections') + .Chat.getModelsArray(); if (chatModels.length > MAX_PIN_COUNT) { let maxPinned = chatModels[MAX_PIN_COUNT - 1].pin; if (maxPinned) { return false; } } - await window.Store.Cmd.pinChat(chat, true); + await window.require('WAWebCmd').Cmd.pinChat(chat, true); return true; }, chatId); } @@ -1524,12 +2073,14 @@

Source: Client.js

* @returns {Promise<boolean>} New pin state */ async unpinChat(chatId) { - return this.pupPage.evaluate(async chatId => { - let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); + return this.pupPage.evaluate(async (chatId) => { + let chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); if (!chat.pin) { return false; } - await window.Store.Cmd.pinChat(chat, false); + await window.require('WAWebCmd').Cmd.pinChat(chat, false); return false; }, chatId); } @@ -1561,14 +2112,29 @@

Source: Client.js

* @param {number} unmuteDateTs Timestamp at which the chat will be unmuted * @returns {Promise<{isMuted: boolean, muteExpiration: number}>} */ - async _muteUnmuteChat (chatId, action, unmuteDateTs) { - return this.pupPage.evaluate(async (chatId, action, unmuteDateTs) => { - const chat = window.Store.Chat.get(chatId) ?? await window.Store.Chat.find(chatId); - action === 'MUTE' - ? await chat.mute.mute({ expiration: unmuteDateTs, sendDevice: true }) - : await chat.mute.unmute({ sendDevice: true }); - return { isMuted: chat.mute.expiration !== 0, muteExpiration: chat.mute.expiration }; - }, chatId, action, unmuteDateTs || -1); + async _muteUnmuteChat(chatId, action, unmuteDateTs) { + return this.pupPage.evaluate( + async (chatId, action, unmuteDateTs) => { + const chat = + window.require('WAWebCollections').Chat.get(chatId) ?? + (await window + .require('WAWebCollections') + .Chat.find(chatId)); + action === 'MUTE' + ? await chat.mute.mute({ + expiration: unmuteDateTs, + sendDevice: true, + }) + : await chat.mute.unmute({ sendDevice: true }); + return { + isMuted: chat.mute.expiration !== 0, + muteExpiration: chat.mute.expiration, + }; + }, + chatId, + action, + unmuteDateTs || -1, + ); } /** @@ -1576,9 +2142,11 @@

Source: Client.js

* @param {string} chatId ID of the chat that will be marked as unread */ async markChatUnread(chatId) { - await this.pupPage.evaluate(async chatId => { - let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - await window.Store.Cmd.markChatUnread(chat, true); + await this.pupPage.evaluate(async (chatId) => { + let chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + await window.require('WAWebCmd').Cmd.markChatUnread(chat, true); }, chatId); } @@ -1588,18 +2156,18 @@

Source: Client.js

* @returns {Promise<string>} */ async getProfilePicUrl(contactId) { - const profilePic = await this.pupPage.evaluate(async contactId => { + const profilePic = await this.pupPage.evaluate(async (contactId) => { try { - const chatWid = window.Store.WidFactory.createWid(contactId); - return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0') - ? await window.Store.ProfilePic.profilePicFind(chatWid) - : await window.Store.ProfilePic.requestProfilePicFromServer(chatWid); + const chat = await window.WWebJS.getChat(contactId); + return await window + .require('WAWebContactProfilePicThumbBridge') + .requestProfilePicFromServer(chat); } catch (err) { - if(err.name === 'ServerStatusCodeError') return undefined; + if (err.name === 'ServerStatusCodeError') return undefined; throw err; } }, contactId); - + return profilePic ? profilePic.eurl : undefined; } @@ -1610,17 +2178,26 @@

Source: Client.js

*/ async getCommonGroups(contactId) { const commonGroups = await this.pupPage.evaluate(async (contactId) => { - let contact = window.Store.Contact.get(contactId); + let contact = window + .require('WAWebCollections') + .Contact.get(contactId); if (!contact) { - const wid = window.Store.WidFactory.createWid(contactId); - const chatConstructor = window.Store.Contact.getModelsArray().find(c=>!c.isGroup).constructor; - contact = new chatConstructor({id: wid}); + const wid = window + .require('WAWebWidFactory') + .createWid(contactId); + const chatConstructor = window + .require('WAWebCollections') + .Contact.getModelsArray() + .find((c) => !c.isGroup).constructor; + contact = new chatConstructor({ id: wid }); } if (contact.commonGroups) { return contact.commonGroups.serialize(); } - const status = await window.Store.findCommonGroups(contact); + const status = await window + .require('WAWebFindCommonGroupsContactAction') + .findCommonGroups(contact); if (status) { return contact.commonGroups.serialize(); } @@ -1635,10 +2212,10 @@

Source: Client.js

/** * Force reset of connection state for the client - */ + */ async resetState() { await this.pupPage.evaluate(() => { - window.Store.AppState.reconnect(); + window.require('WAWebSocketModel').Socket.reconnect(); }); } @@ -1652,7 +2229,7 @@

Source: Client.js

} /** - * Get the registered WhatsApp ID for a number. + * Get the registered WhatsApp ID for a number. * Will return null if the number is not registered on WhatsApp. * @param {string} number Number or ID ("@c.us" will be automatically appended if not specified) * @returns {Promise<Object|null>} @@ -1662,9 +2239,11 @@

Source: Client.js

number += '@c.us'; } - return await this.pupPage.evaluate(async number => { - const wid = window.Store.WidFactory.createWid(number); - const result = await window.Store.QueryExist(wid); + return await this.pupPage.evaluate(async (number) => { + const wid = window.require('WAWebWidFactory').createWid(number); + const result = await window + .require('WAWebQueryExistsJob') + .queryWidExists(wid); if (!result || result.wid === undefined) return null; return result.wid; }, number); @@ -1676,11 +2255,15 @@

Source: Client.js

* @returns {Promise<string>} */ async getFormattedNumber(number) { - if (!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net'); - if (!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`; - - return await this.pupPage.evaluate(async numberId => { - return window.Store.NumberInfo.formattedPhoneNumber(numberId); + if (!number.endsWith('@s.whatsapp.net')) + number = number.replace('c.us', 's.whatsapp.net'); + if (!number.includes('@s.whatsapp.net')) + number = `${number}@s.whatsapp.net`; + + return await this.pupPage.evaluate(async (numberId) => { + return window + .require('WAWebPhoneUtils') + .formattedPhoneNumber(numberId); }, number); } @@ -1692,8 +2275,8 @@

Source: Client.js

async getCountryCode(number) { number = number.replace(' ', '').replace('+', '').replace('@c.us', ''); - return await this.pupPage.evaluate(async numberId => { - return window.Store.NumberInfo.findCC(numberId); + return await this.pupPage.evaluate(async (numberId) => { + return window.require('WAPhoneFindCC').findCC(numberId); }, number); } @@ -1739,92 +2322,137 @@

Source: Client.js

*/ async createGroup(title, participants = [], options = {}) { !Array.isArray(participants) && (participants = [participants]); - participants.map(p => (p instanceof Contact) ? p.id._serialized : p); - - return await this.pupPage.evaluate(async (title, participants, options) => { - const { - messageTimer = 0, - parentGroupId, - autoSendInviteV4 = true, - comment = '', - } = options; - const participantData = {}, participantWids = [], failedParticipants = []; - let createGroupResult, parentGroupWid; - - const addParticipantResultCodes = { - default: 'An unknown error occupied while adding a participant', - 200: 'The participant was added successfully', - 403: 'The participant can be added by sending private invitation only', - 404: 'The phone number is not registered on WhatsApp' - }; - - for (const participant of participants) { - const pWid = window.Store.WidFactory.createWid(participant); - if ((await window.Store.QueryExist(pWid))?.wid) { - participantWids.push({ phoneNumber: pWid }); + participants.map((p) => (p instanceof Contact ? p.id._serialized : p)); + + return await this.pupPage.evaluate( + async (title, participants, options) => { + const { + messageTimer = 0, + parentGroupId, + autoSendInviteV4 = true, + comment = '', + } = options; + const participantData = {}, + participantWids = [], + failedParticipants = []; + let createGroupResult, parentGroupWid; + + const addParticipantResultCodes = { + default: + 'An unknown error occupied while adding a participant', + 200: 'The participant was added successfully', + 403: 'The participant can be added by sending private invitation only', + 404: 'The phone number is not registered on WhatsApp', + }; + + for (const participant of participants) { + const pWid = window + .require('WAWebWidFactory') + .createWid(participant); + if ( + ( + await window + .require('WAWebQueryExistsJob') + .queryWidExists(pWid) + )?.wid + ) { + participantWids.push({ phoneNumber: pWid }); + } else failedParticipants.push(participant); } - else failedParticipants.push(participant); - } - parentGroupId && (parentGroupWid = window.Store.WidFactory.createWid(parentGroupId)); + parentGroupId && + (parentGroupWid = window + .require('WAWebWidFactory') + .createWid(parentGroupId)); + + try { + createGroupResult = await window + .require('WAWebGroupCreateJob') + .createGroup( + { + addressingModeOverride: 'lid', + memberAddMode: options.memberAddMode ?? false, + membershipApprovalMode: + options.membershipApprovalMode ?? false, + announce: options.announce ?? false, + restrict: + options.isRestrict !== undefined + ? !options.isRestrict + : false, + ephemeralDuration: messageTimer, + parentGroupId: parentGroupWid, + title: title, + }, + participantWids, + ); + } catch (err) { + return 'CreateGroupError: An unknown error occupied while creating a group'; + } - try { - createGroupResult = await window.Store.GroupUtils.createGroup( - { - 'addressingModeOverride': 'lid', - 'memberAddMode': options.memberAddMode ?? false, - 'membershipApprovalMode': options.membershipApprovalMode ?? false, - 'announce': options.announce ?? false, - 'restrict': options.isRestrict !== undefined ? !options.isRestrict : false, - 'ephemeralDuration': messageTimer, - 'parentGroupId': parentGroupWid, - 'title': title, - }, - participantWids - ); - } catch (err) { - return 'CreateGroupError: An unknown error occupied while creating a group'; - } + for (const participant of createGroupResult.participants) { + let isInviteV4Sent = false; + participant.wid.server == 'lid' && + (participant.wid = window + .require('WAWebApiContact') + .getPhoneNumber(participant.wid)); + const participantId = participant.wid._serialized; + const statusCode = participant.error || 200; + + if (autoSendInviteV4 && statusCode === 403) { + window + .require('WAWebCollections') + .Contact.gadd(participant.wid, { silent: true }); + const addParticipantResult = await window + .require('WAWebChatSendMessages') + .sendGroupInviteMessage( + window + .require('WAWebCollections') + .Chat.get(participant.wid) || + (await window + .require('WAWebCollections') + .Chat.find(participant.wid)), + createGroupResult.wid._serialized, + createGroupResult.subject, + participant.invite_code, + participant.invite_code_exp, + comment, + await window.WWebJS.getProfilePicThumbToBase64( + createGroupResult.wid, + ), + ); + isInviteV4Sent = + addParticipantResult.messageSendResult === 'OK'; + } - for (const participant of createGroupResult.participants) { - let isInviteV4Sent = false; - participant.wid.server == 'lid' && (participant.wid = window.Store.LidUtils.getPhoneNumber(participant.wid)); - const participantId = participant.wid._serialized; - const statusCode = participant.error || 200; - - if (autoSendInviteV4 && statusCode === 403) { - window.Store.Contact.gadd(participant.wid, { silent: true }); - const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage( - window.Store.Chat.get(participant.wid) || await window.Store.Chat.find(participant.wid), - createGroupResult.wid._serialized, - createGroupResult.subject, - participant.invite_code, - participant.invite_code_exp, - comment, - await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid) - ); - isInviteV4Sent = addParticipantResult.messageSendResult === 'OK'; + participantData[participantId] = { + statusCode: statusCode, + message: + addParticipantResultCodes[statusCode] || + addParticipantResultCodes.default, + isGroupCreator: participant.type === 'superadmin', + isInviteV4Sent: isInviteV4Sent, + }; } - participantData[participantId] = { - statusCode: statusCode, - message: addParticipantResultCodes[statusCode] || addParticipantResultCodes.default, - isGroupCreator: participant.type === 'superadmin', - isInviteV4Sent: isInviteV4Sent - }; - } + for (const f of failedParticipants) { + participantData[f] = { + statusCode: 404, + message: addParticipantResultCodes[404], + isGroupCreator: false, + isInviteV4Sent: false, + }; + } - for (const f of failedParticipants) { - participantData[f] = { - statusCode: 404, - message: addParticipantResultCodes[404], - isGroupCreator: false, - isInviteV4Sent: false + return { + title: title, + gid: createGroupResult.wid, + participants: participantData, }; - } - - return { title: title, gid: createGroupResult.wid, participants: participantData }; - }, title, participants, options); + }, + title, + participants, + options, + ); } /** @@ -1849,46 +2477,61 @@

Source: Client.js

/** * Creates a new channel * @param {string} title The channel name - * @param {CreateChannelOptions} options + * @param {CreateChannelOptions} options * @returns {Promise<CreateChannelResult|string>} Returns an object that handles the result for the channel creation or an error message as a string */ async createChannel(title, options = {}) { - return await this.pupPage.evaluate(async (title, options) => { - let response, { description = null, picture = null } = options; - - if (!window.Store.ChannelUtils.isNewsletterCreationEnabled()) { - return 'CreateChannelError: A channel creation is not enabled'; - } + return await this.pupPage.evaluate( + async (title, options) => { + let response, + { description = null, picture = null } = options; + + if ( + !window + .require('WAWebNewsletterGatingUtils') + .isNewsletterCreationEnabled() + ) { + return 'CreateChannelError: A channel creation is not enabled'; + } - if (picture) { - picture = await window.WWebJS.cropAndResizeImage(picture, { - asDataUrl: true, - mimetype: 'image/jpeg', - size: 640, - quality: 1 - }); - } + if (picture) { + picture = await window.WWebJS.cropAndResizeImage(picture, { + asDataUrl: true, + mimetype: 'image/jpeg', + size: 640, + quality: 1, + }); + } - try { - response = await window.Store.ChannelUtils.createNewsletterQuery({ - name: title, - description: description, - picture: picture, - }); - } catch (err) { - if (err.name === 'ServerStatusCodeError') { - return 'CreateChannelError: An error occupied while creating a channel'; + try { + response = await window + .require('WAWebNewsletterCreateQueryJob') + .createNewsletterQuery({ + name: title, + description: description, + picture: picture, + }); + } catch (err) { + if (err.name === 'ServerStatusCodeError') { + return 'CreateChannelError: An error occupied while creating a channel'; + } + throw err; } - throw err; - } - return { - title: title, - nid: window.Store.JidToWid.newsletterJidToWid(response.idJid), - inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`, - createdAtTs: response.newsletterCreationTimeMetadataMixin.creationTimeValue - }; - }, title, options); + return { + title: title, + nid: window + .require('WAWebJidToWid') + .newsletterJidToWid(response.idJid), + inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`, + createdAtTs: + response.newsletterCreationTimeMetadataMixin + .creationTimeValue, + }; + }, + title, + options, + ); } /** @@ -1898,7 +2541,10 @@

Source: Client.js

*/ async subscribeToChannel(channelId) { return await this.pupPage.evaluate(async (channelId) => { - return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Subscribe'); + return await window.WWebJS.subscribeToUnsubscribeFromChannel( + channelId, + 'Subscribe', + ); }, channelId); } @@ -1915,9 +2561,17 @@

Source: Client.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async unsubscribeFromChannel(channelId, options) { - return await this.pupPage.evaluate(async (channelId, options) => { - return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Unsubscribe', options); - }, channelId, options); + return await this.pupPage.evaluate( + async (channelId, options) => { + return await window.WWebJS.subscribeToUnsubscribeFromChannel( + channelId, + 'Unsubscribe', + options, + ); + }, + channelId, + options, + ); } /** @@ -1935,26 +2589,51 @@

Source: Client.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async transferChannelOwnership(channelId, newOwnerId, options = {}) { - return await this.pupPage.evaluate(async (channelId, newOwnerId, options) => { - const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); - const newOwner = window.Store.Contact.get(newOwnerId) || (await window.Store.Contact.find(newOwnerId)); - if (!channel.newsletterMetadata) { - await window.Store.NewsletterMetadataCollection.update(channel.id); - } - - try { - await window.Store.ChannelUtils.changeNewsletterOwnerAction(channel, newOwner); + return await this.pupPage.evaluate( + async (channelId, newOwnerId, options) => { + const channel = await window.WWebJS.getChat(channelId, { + getAsModel: false, + }); + const newOwner = + window + .require('WAWebCollections') + .Contact.get(newOwnerId) || + (await window + .require('WAWebCollections') + .Contact.find(newOwnerId)); + if (!channel.newsletterMetadata) { + await window + .require('WAWebCollections') + .NewsletterMetadataCollection.update(channel.id); + } - if (options.shouldDismissSelfAsAdmin) { - const meContact = window.Store.ContactCollection.getMeContact(); - meContact && (await window.Store.ChannelUtils.demoteNewsletterAdminAction(channel, meContact)); + try { + await window + .require('WAWebChangeNewsletterOwnerAction') + .changeNewsletterOwnerAction(channel, newOwner); + + if (options.shouldDismissSelfAsAdmin) { + const meContact = window + .require('WAWebContactCollection') + .getMeContact(); + meContact && + (await window + .require('WAWebNewsletterDemoteAdminJob') + .demoteNewsletterAdminAction( + channel, + meContact, + )); + } + } catch (error) { + return false; } - } catch (error) { - return false; - } - return true; - }, channelId, newOwnerId, options); + return true; + }, + channelId, + newOwnerId, + options, + ); } /** @@ -1977,48 +2656,78 @@

Source: Client.js

* @returns {Promise<Array<Channel>>} Returns an array of Channel objects or an empty array if no channels were found */ async searchChannels(searchOptions = {}) { - return await this.pupPage.evaluate(async ({ - searchText = '', - countryCodes = [window.Store.ChannelUtils.currentRegion], - skipSubscribedNewsletters = false, - view = 0, - limit = 50 - }) => { - searchText = searchText.trim(); - const currentRegion = window.Store.ChannelUtils.currentRegion; - if (![0, 1, 2, 3].includes(view)) view = 0; - - countryCodes = countryCodes.length === 1 && countryCodes[0] === currentRegion - ? countryCodes - : countryCodes.filter((code) => Object.keys(window.Store.ChannelUtils.countryCodesIso).includes(code)); - - const viewTypeMapping = { - 0: 'RECOMMENDED', - 1: 'TRENDING', - 2: 'POPULAR', - 3: 'NEW' - }; - - searchOptions = { - searchText: searchText, - countryCodes: countryCodes, - skipSubscribedNewsletters: skipSubscribedNewsletters, - view: viewTypeMapping[view], - categories: [], - cursorToken: '' - }; - - const originalFunction = window.Store.ChannelUtils.getNewsletterDirectoryPageSize; - limit !== 50 && (window.Store.ChannelUtils.getNewsletterDirectoryPageSize = () => limit); - - const channels = (await window.Store.ChannelUtils.fetchNewsletterDirectories(searchOptions)).newsletters; - - limit !== 50 && (window.Store.ChannelUtils.getNewsletterDirectoryPageSize = originalFunction); - - return channels - ? await Promise.all(channels.map((channel) => window.WWebJS.getChatModel(channel, { isChannel: true }))) - : []; - }, searchOptions); + return await this.pupPage.evaluate( + async ({ + searchText = '', + countryCodes = [], + skipSubscribedNewsletters = false, + view = 0, + limit = 50, + }) => { + searchText = searchText.trim(); + const currentRegion = window.require('WAWebL10N').getRegion(); + if (countryCodes.length === 0) countryCodes[0] = currentRegion; + if (![0, 1, 2, 3].includes(view)) view = 0; + + const { countryCodesIso } = window.require( + 'WAWebCountriesNativeCountryNames', + ); + + countryCodes = + countryCodes.length === 1 && + countryCodes[0] === currentRegion + ? countryCodes + : countryCodes.filter((code) => + Object.keys(countryCodesIso).includes(code), + ); + + const viewTypeMapping = { + 0: 'RECOMMENDED', + 1: 'TRENDING', + 2: 'POPULAR', + 3: 'NEW', + }; + + searchOptions = { + searchText: searchText, + countryCodes: countryCodes, + skipSubscribedNewsletters: skipSubscribedNewsletters, + view: viewTypeMapping[view], + categories: [], + cursorToken: '', + }; + + const originalFunction = window.require( + 'WAWebNewsletterGatingUtils', + ).getNewsletterDirectoryPageSize; + limit !== 50 && + (window.require( + 'WAWebNewsletterGatingUtils', + ).getNewsletterDirectoryPageSize = () => limit); + + const channels = ( + await window + .require('WAWebNewsletterDirectorySearchAction') + .fetchNewsletterDirectories(searchOptions) + ).newsletters; + + limit !== 50 && + (window.require( + 'WAWebNewsletterGatingUtils', + ).getNewsletterDirectoryPageSize = originalFunction); + + return channels + ? await Promise.all( + channels.map((channel) => + window.WWebJS.getChatModel(channel, { + isChannel: true, + }), + ), + ) + : []; + }, + searchOptions, + ); } /** @@ -2027,11 +2736,15 @@

Source: Client.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async deleteChannel(channelId) { - return await this.client.pupPage.evaluate(async (channelId) => { - const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); + return await this.pupPage.evaluate(async (channelId) => { + const channel = await window.WWebJS.getChat(channelId, { + getAsModel: false, + }); if (!channel) return false; try { - await window.Store.ChannelUtils.deleteNewsletterAction(channel); + await window + .require('WAWebNewsletterDeleteAction') + .deleteNewsletterAction(channel); return true; } catch (err) { if (err.name === 'ServerStatusCodeError') return false; @@ -2049,7 +2762,7 @@

Source: Client.js

return window.WWebJS.getLabels(); }); - return labels.map(data => new Label(this, data)); + return labels.map((data) => new Label(this, data)); } /** @@ -2060,7 +2773,7 @@

Source: Client.js

const broadcasts = await this.pupPage.evaluate(async () => { return window.WWebJS.getAllStatuses(); }); - return broadcasts.map(data => new Broadcast(this, data)); + return broadcasts.map((data) => new Broadcast(this, data)); } /** @@ -2072,9 +2785,11 @@

Source: Client.js

const broadcast = await this.pupPage.evaluate(async (userId) => { let status; try { - status = window.Store.Status.get(userId); + status = window.require('WAWebCollections').Status.get(userId); if (!status) { - status = await window.Store.Status.find(userId); + status = await window + .require('WAWebCollections') + .Status.find(userId); } } catch { status = null; @@ -2092,17 +2807,26 @@

Source: Client.js

*/ async revokeStatusMessage(messageId) { return await this.pupPage.evaluate(async (msgId) => { - const status = window.Store.Status.getMyStatus(); + const status = window + .require('WAWebCollections') + .Status.getMyStatus(); if (!status) return; const msg = - window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; if (!msg) return; if (!msg.id.fromMe || !msg.id.remote.isStatus()) throw 'Invalid usage! Can only revoke the message its from own status broadcast'; - return await window.Store.StatusUtils.sendStatusRevokeMsgAction(status, msg); + return await window + .require('WAWebRevokeStatusAction') + .sendStatusRevokeMsgAction(status, msg); }, messageId); } @@ -2120,7 +2844,7 @@

Source: Client.js

} /** - * Get all Labels assigned to a chat + * Get all Labels assigned to a chat * @param {string} chatId * @returns {Promise<Array<Label>>} */ @@ -2129,7 +2853,7 @@

Source: Client.js

return window.WWebJS.getChatLabels(chatId); }, chatId); - return labels.map(data => new Label(this, data)); + return labels.map((data) => new Label(this, data)); } /** @@ -2139,7 +2863,7 @@

Source: Client.js

*/ async getChatsByLabelId(labelId) { const chatIds = await this.pupPage.evaluate(async (labelId) => { - const label = window.Store.Label.get(labelId); + const label = window.require('WAWebCollections').Label.get(labelId); const labelItems = label.labelItemCollection.getModelsArray(); return labelItems.reduce((result, item) => { if (item.parentType === 'Chat') { @@ -2149,7 +2873,7 @@

Source: Client.js

}, []); }, labelId); - return Promise.all(chatIds.map(id => this.getChatById(id))); + return Promise.all(chatIds.map((id) => this.getChatById(id))); } /** @@ -2158,11 +2882,18 @@

Source: Client.js

*/ async getBlockedContacts() { const blockedContacts = await this.pupPage.evaluate(() => { - let chatIds = window.Store.Blocklist.getModelsArray().map(a => a.id._serialized); - return Promise.all(chatIds.map(id => window.WWebJS.getContact(id))); + let chatIds = window + .require('WAWebCollections') + .Blocklist.getModelsArray() + .map((a) => a.id._serialized); + return Promise.all( + chatIds.map((id) => window.WWebJS.getContact(id)), + ); }); - return blockedContacts.map(contact => ContactFactory.create(this.client, contact)); + return blockedContacts.map((contact) => + ContactFactory.create(this.client, contact), + ); } /** @@ -2171,9 +2902,13 @@

Source: Client.js

* @returns {Promise<boolean>} Returns true if the picture was properly updated. */ async setProfilePicture(media) { - const success = await this.pupPage.evaluate((chatid, media) => { - return window.WWebJS.setPicture(chatid, media); - }, this.info.wid._serialized, media); + const success = await this.pupPage.evaluate( + (chatid, media) => { + return window.WWebJS.setPicture(chatid, media); + }, + this.info.wid._serialized, + media, + ); return success; } @@ -2189,7 +2924,7 @@

Source: Client.js

return success; } - + /** * Change labels in chats * @param {Array<number|string>} labelIds @@ -2197,26 +2932,42 @@

Source: Client.js

* @returns {Promise<void>} */ async addOrRemoveLabels(labelIds, chatIds) { + return this.pupPage.evaluate( + async (labelIds, chatIds) => { + if ( + ['smba', 'smbi'].indexOf( + window.require('WAWebConnModel').Conn.platform, + ) === -1 + ) { + throw '[LT01] Only Whatsapp business'; + } + const labels = window.WWebJS.getLabels().filter( + (e) => labelIds.find((l) => l == e.id) !== undefined, + ); + const chats = window + .require('WAWebCollections') + .Chat.filter((e) => chatIds.includes(e.id._serialized)); - return this.pupPage.evaluate(async (labelIds, chatIds) => { - if (['smba', 'smbi'].indexOf(window.Store.Conn.platform) === -1) { - throw '[LT01] Only Whatsapp business'; - } - const labels = window.WWebJS.getLabels().filter(e => labelIds.find(l => l == e.id) !== undefined); - const chats = window.Store.Chat.filter(e => chatIds.includes(e.id._serialized)); - - let actions = labels.map(label => ({id: label.id, type: 'add'})); + let actions = labels.map((label) => ({ + id: label.id, + type: 'add', + })); - chats.forEach(chat => { - (chat.labels || []).forEach(n => { - if (!actions.find(e => e.id == n)) { - actions.push({id: n, type: 'remove'}); - } + chats.forEach((chat) => { + (chat.labels || []).forEach((n) => { + if (!actions.find((e) => e.id == n)) { + actions.push({ id: n, type: 'remove' }); + } + }); }); - }); - return await window.Store.Label.addOrRemoveLabels(actions, chats); - }, labelIds, chatIds); + return await window + .require('WAWebCollections') + .Label.addOrRemoveLabels(actions, chats); + }, + labelIds, + chatIds, + ); } /** @@ -2236,8 +2987,12 @@

Source: Client.js

*/ async getGroupMembershipRequests(groupId) { return await this.pupPage.evaluate(async (groupId) => { - const groupWid = window.Store.WidFactory.createWid(groupId); - return await window.Store.MembershipRequestUtils.getMembershipApprovalRequests(groupWid); + const groupWid = window + .require('WAWebWidFactory') + .createWid(groupId); + return await window + .require('WAWebApiMembershipApprovalRequestStore') + .getMembershipApprovalRequests(groupWid); }, groupId); } @@ -2263,10 +3018,19 @@

Source: Client.js

* @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned */ async approveGroupMembershipRequests(groupId, options = {}) { - return await this.pupPage.evaluate(async (groupId, options) => { - const { requesterIds = null, sleep = [250, 500] } = options; - return await window.WWebJS.membershipRequestAction(groupId, 'Approve', requesterIds, sleep); - }, groupId, options); + return await this.pupPage.evaluate( + async (groupId, options) => { + const { requesterIds = null, sleep = [250, 500] } = options; + return await window.WWebJS.membershipRequestAction( + groupId, + 'Approve', + requesterIds, + sleep, + ); + }, + groupId, + options, + ); } /** @@ -2276,24 +3040,36 @@

Source: Client.js

* @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned */ async rejectGroupMembershipRequests(groupId, options = {}) { - return await this.pupPage.evaluate(async (groupId, options) => { - const { requesterIds = null, sleep = [250, 500] } = options; - return await window.WWebJS.membershipRequestAction(groupId, 'Reject', requesterIds, sleep); - }, groupId, options); + return await this.pupPage.evaluate( + async (groupId, options) => { + const { requesterIds = null, sleep = [250, 500] } = options; + return await window.WWebJS.membershipRequestAction( + groupId, + 'Reject', + requesterIds, + sleep, + ); + }, + groupId, + options, + ); } - /** * Setting autoload download audio * @param {boolean} flag true/false */ async setAutoDownloadAudio(flag) { - await this.pupPage.evaluate(async flag => { - const autoDownload = window.Store.Settings.getAutoDownloadAudio(); + await this.pupPage.evaluate(async (flag) => { + const autoDownload = window + .require('WAWebUserPrefsGeneral') + .getAutoDownloadAudio(); if (autoDownload === flag) { return flag; } - await window.Store.Settings.setAutoDownloadAudio(flag); + await window + .require('WAWebUserPrefsGeneral') + .setAutoDownloadAudio(flag); return flag; }, flag); } @@ -2303,12 +3079,16 @@

Source: Client.js

* @param {boolean} flag true/false */ async setAutoDownloadDocuments(flag) { - await this.pupPage.evaluate(async flag => { - const autoDownload = window.Store.Settings.getAutoDownloadDocuments(); + await this.pupPage.evaluate(async (flag) => { + const autoDownload = window + .require('WAWebUserPrefsGeneral') + .getAutoDownloadDocuments(); if (autoDownload === flag) { return flag; } - await window.Store.Settings.setAutoDownloadDocuments(flag); + await window + .require('WAWebUserPrefsGeneral') + .setAutoDownloadDocuments(flag); return flag; }, flag); } @@ -2318,12 +3098,16 @@

Source: Client.js

* @param {boolean} flag true/false */ async setAutoDownloadPhotos(flag) { - await this.pupPage.evaluate(async flag => { - const autoDownload = window.Store.Settings.getAutoDownloadPhotos(); + await this.pupPage.evaluate(async (flag) => { + const autoDownload = window + .require('WAWebUserPrefsGeneral') + .getAutoDownloadPhotos(); if (autoDownload === flag) { return flag; } - await window.Store.Settings.setAutoDownloadPhotos(flag); + await window + .require('WAWebUserPrefsGeneral') + .setAutoDownloadPhotos(flag); return flag; }, flag); } @@ -2333,12 +3117,16 @@

Source: Client.js

* @param {boolean} flag true/false */ async setAutoDownloadVideos(flag) { - await this.pupPage.evaluate(async flag => { - const autoDownload = window.Store.Settings.getAutoDownloadVideos(); + await this.pupPage.evaluate(async (flag) => { + const autoDownload = window + .require('WAWebUserPrefsGeneral') + .getAutoDownloadVideos(); if (autoDownload === flag) { return flag; } - await window.Store.Settings.setAutoDownloadVideos(flag); + await window + .require('WAWebUserPrefsGeneral') + .setAutoDownloadVideos(flag); return flag; }, flag); } @@ -2350,16 +3138,20 @@

Source: Client.js

* @returns {Promise<boolean>} */ async setBackgroundSync(flag) { - return await this.pupPage.evaluate(async flag => { - const backSync = window.Store.Settings.getGlobalOfflineNotifications(); + return await this.pupPage.evaluate(async (flag) => { + const backSync = window + .require('WAWebUserPrefsNotifications') + .getGlobalOfflineNotifications(); if (backSync === flag) { return flag; } - await window.Store.Settings.setGlobalOfflineNotifications(flag); + await window + .require('WAWebUserPrefsNotifications') + .setGlobalOfflineNotifications(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 @@ -2369,8 +3161,17 @@

Source: Client.js

*/ async getContactDeviceCount(userId) { return await this.pupPage.evaluate(async (userId) => { - const devices = await window.Store.DeviceList.getDeviceIds([window.Store.WidFactory.createWid(userId)]); - if (devices && devices.length && devices[0] != null && typeof devices[0].devices == 'object') { + const devices = await window + .require('WAWebApiDeviceList') + .getDeviceIds([ + window.require('WAWebWidFactory').createWid(userId), + ]); + if ( + devices && + devices.length && + devices[0] != null && + typeof devices[0].devices == 'object' + ) { return devices[0].devices.length; } return 0; @@ -2384,18 +3185,22 @@

Source: Client.js

*/ async syncHistory(chatId) { return await this.pupPage.evaluate(async (chatId) => { - const chatWid = window.Store.WidFactory.createWid(chatId); - const chat = window.Store.Chat.get(chatWid) ?? (await window.Store.Chat.find(chatWid)); + const chatWid = window.require('WAWebWidFactory').createWid(chatId); + const chat = + window.require('WAWebCollections').Chat.get(chatWid) ?? + (await window.require('WAWebCollections').Chat.find(chatWid)); if (chat?.endOfHistoryTransferType === 0) { - await window.Store.HistorySync.sendPeerDataOperationRequest(3, { - chatId: chat.id - }); + await window + .require('WAWebSendNonMessageDataRequest') + .sendPeerDataOperationRequest(3, { + chatId: chat.id, + }); return true; } return false; }, chatId); } - + /** * Generates a WhatsApp call link (video call or voice call) * @param {Date} startTime The start time of the call @@ -2404,17 +3209,27 @@

Source: Client.js

*/ async createCallLink(startTime, callType) { if (!['video', 'voice'].includes(callType)) { - throw new class CreateCallLinkError extends Error { - constructor(m) { super(m); } - }('Invalid \'callType\' parameter value is provided. Valid values are: \'voice\' | \'video\'.'); + throw new (class CreateCallLinkError extends Error { + constructor(m) { + super(m); + } + })( + "Invalid 'callType' parameter value is provided. Valid values are: 'voice' | 'video'.", + ); } startTime = Math.floor(startTime.getTime() / 1000); - - return await this.pupPage.evaluate(async (startTimeTs, callType) => { - const response = await window.Store.ScheduledEventMsgUtils.createEventCallLink(startTimeTs, callType); - return response ?? ''; - }, startTime, callType); + + return await this.pupPage.evaluate( + async (startTimeTs, callType) => { + const response = await window + .require('WAWebGenerateEventCallLink') + .createEventCallLink(startTimeTs, callType); + return response ?? ''; + }, + startTime, + callType, + ); } /** @@ -2426,35 +3241,59 @@

Source: Client.js

async sendResponseToScheduledEvent(response, eventMessageId) { if (![0, 1, 2, 3].includes(response)) return false; - return await this.pupPage.evaluate(async (response, msgId) => { - const eventMsg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; - if (!eventMsg) return false; - - await window.Store.ScheduledEventMsgUtils.sendEventResponseMsg(response, eventMsg); - return true; - }, response, eventMessageId); + return await this.pupPage.evaluate( + async (response, msgId) => { + const eventMsg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + if (!eventMsg) return false; + + await window + .require('WAWebSendEventResponseMsgAction') + .sendEventResponseMsg(response, eventMsg); + return true; + }, + response, + eventMessageId, + ); } - + /** * Save new contact to user's addressbook or edit the existing one * @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code - * @param {string} firstName - * @param {string} lastName + * @param {string} firstName + * @param {string} lastName * @param {boolean} [syncToAddressbook = false] If set to true, the contact will also be saved to the user's address book on their phone. False by default * @returns {Promise<void>} */ - async saveOrEditAddressbookContact(phoneNumber, firstName, lastName, syncToAddressbook = false) - { - return await this.pupPage.evaluate(async (phoneNumber, firstName, lastName, syncToAddressbook) => { - return await window.Store.AddressbookContactUtils.saveContactAction({ - 'firstName' : firstName, - 'lastName' : lastName, - 'phoneNumber' : phoneNumber, - 'prevPhoneNumber' : phoneNumber, - 'syncToAddressbook': syncToAddressbook, - 'username' : undefined - }); - }, phoneNumber, firstName, lastName, syncToAddressbook); + async saveOrEditAddressbookContact( + phoneNumber, + firstName, + lastName, + syncToAddressbook = false, + ) { + return await this.pupPage.evaluate( + async (phoneNumber, firstName, lastName, syncToAddressbook) => { + return await window + .require('WAWebSaveContactAction') + .saveContactAction({ + firstName: firstName, + lastName: lastName, + phoneNumber: phoneNumber, + prevPhoneNumber: phoneNumber, + syncToAddressbook: syncToAddressbook, + username: undefined, + }); + }, + phoneNumber, + firstName, + lastName, + syncToAddressbook, + ); } /** @@ -2462,11 +3301,14 @@

Source: Client.js

* @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code * @returns {Promise<void>} */ - async deleteAddressbookContact(phoneNumber) - { + async deleteAddressbookContact(phoneNumber) { return await this.pupPage.evaluate(async (phoneNumber) => { - const wid = window.Store.WidFactory.createWid(phoneNumber); - return await window.Store.AddressbookContactUtils.deleteContactAction({phoneNumber: wid}); + const wid = window + .require('WAWebWidFactory') + .createWid(phoneNumber); + return await window + .require('WAWebDeleteContactAction') + .deleteContactAction({ phoneNumber: wid }); }, phoneNumber); } @@ -2479,14 +3321,17 @@

Source: Client.js

return await this.pupPage.evaluate(async (userIds) => { if (!Array.isArray(userIds)) userIds = [userIds]; - return await Promise.all(userIds.map(async (userId) => { - const { lid, phone } = await window.WWebJS.enforceLidAndPnRetrieval(userId); + return await Promise.all( + userIds.map(async (userId) => { + const { lid, phone } = + await window.WWebJS.enforceLidAndPnRetrieval(userId); - return { - lid: lid?._serialized, - pn: phone?._serialized - }; - })); + return { + lid: lid?._serialized, + pn: phone?._serialized, + }; + }), + ); }, userIds); } @@ -2498,15 +3343,28 @@

Source: Client.js

* @returns {Promise<void>} */ async addOrEditCustomerNote(userId, note) { - return await this.pupPage.evaluate(async (userId, note) => { - if (!window.Store.BusinessGatingUtils.smbNotesV1Enabled()) return; + return await this.pupPage.evaluate( + async (userId, note) => { + if (!window.require('WAWebBizGatingUtils').smbNotesV1Enabled()) + return; - return window.Store.CustomerNoteUtils.noteAddAction( - 'unstructured', - window.Store.WidToJid.widToUserJid(window.Store.WidFactory.createWid(userId)), - note - ); - }, userId, note); + return window + .require('WAWebNoteAction') + .noteAddAction( + 'unstructured', + window + .require('WAWebWidToJid') + .widToUserJid( + window + .require('WAWebWidFactory') + .createWid(userId), + ), + note, + ); + }, + userId, + note, + ); } /** @@ -2524,71 +3382,88 @@

Source: Client.js

*/ async getCustomerNote(userId) { return await this.pupPage.evaluate(async (userId) => { - if (!window.Store.BusinessGatingUtils.smbNotesV1Enabled()) return null; - - const note = await window.Store.CustomerNoteUtils.retrieveOnlyNoteForChatJid( - window.Store.WidToJid.widToUserJid(window.Store.WidFactory.createWid(userId)) - ); + if (!window.require('WAWebBizGatingUtils').smbNotesV1Enabled()) + return null; + + const note = await window + .require('WAWebNoteAction') + .retrieveOnlyNoteForChatJid( + window + .require('WAWebWidToJid') + .widToUserJid( + window.require('WAWebWidFactory').createWid(userId), + ), + ); let serialized = note?.serialize(); if (!serialized) return null; - serialized.chatId = window.Store.JidToWid.userJidToUserWid(serialized.chatJid)._serialized; + serialized.chatId = window + .require('WAWebJidToWid') + .userJidToUserWid(serialized.chatJid)._serialized; delete serialized.chatJid; return serialized; }, userId); } - + /** * Get Poll Votes * @param {string} messageId - * @return {Promise<Array<PollVote>>} + * @return {Promise<Array<PollVote>>} */ async getPollVotes(messageId) { const msg = await this.getMessageById(messageId); if (!msg) return []; - if (msg.type != MessageTypes.POLL_CREATION) throw 'Invalid usage! Can only be used with a pollCreation message'; - - const pollVotes = await this.pupPage.evaluate( async (msg) => { - const msgKey = window.Store.MsgKey.fromString(msg.id._serialized); - let pollVotes = await window.Store.PollsVotesSchema.getTable().equals(['parentMsgKey'], msgKey.toString()); - - return pollVotes.map(item => { + if (msg.type != MessageTypes.POLL_CREATION) + throw 'Invalid usage! Can only be used with a pollCreation message'; + + const pollVotes = await this.pupPage.evaluate(async (msg) => { + const msgKey = window + .require('WAWebMsgKey') + .fromString(msg.id._serialized); + let pollVotes = await window + .require('WAWebPollsVotesSchema') + .getTable() + .equals(['parentMsgKey'], msgKey.toString()); + + return pollVotes.map((item) => { const typedArray = new Uint8Array(item.selectedOptionLocalIds); return { ...item, - selectedOptionLocalIds: Array.from(typedArray) + selectedOptionLocalIds: Array.from(typedArray), }; }); }, msg); - return pollVotes.map((pollVote) => new PollVote(this.client, {...pollVote, parentMessage: msg})); + return pollVotes.map( + (pollVote) => + new PollVote(this.client, { ...pollVote, parentMessage: msg }), + ); } } module.exports = Client;
-
-
-
- -
-
-
- -
- - - - - - - - - \ No newline at end of file + +
+
+ +

+ +
+ +
+ + + + + + + + diff --git a/docs/GroupChat.html b/docs/GroupChat.html index d95e9707f76..4e59758cc1a 100644 --- a/docs/GroupChat.html +++ b/docs/GroupChat.html @@ -54,6 +54,9 @@

Properties

isGroup
+
isLocked
+
+
@@ -73,13 +76,13 @@

Properties

name
+
owner
+
+
-
owner
-
-
participants
@@ -272,6 +275,13 @@

isGroupChat#isGroup

+

isLocked +  unknown

+

Indicates if the Chat is locked

+
+
Inherited from
+
Chat#isLocked
+

isMuted  unknown

Indicates if the chat is muted or not

@@ -1294,7 +1304,7 @@

unpin diff --git a/docs/PrivateChat.html b/docs/PrivateChat.html index ab511574a23..189df3860fc 100644 --- a/docs/PrivateChat.html +++ b/docs/PrivateChat.html @@ -48,13 +48,16 @@

Properties

isGroup
-
isMuted
+
isLocked

+
isMuted
+
+
isReadOnly
@@ -64,13 +67,13 @@

Properties

muteExpiration
-
name
-
-
+
name
+
+
pinned
@@ -199,6 +202,13 @@

isGroupChat#isGroup

+

isLocked +  unknown

+

Indicates if the Chat is locked

+
+
Inherited from
+
Chat#isLocked
+

isMuted  unknown

Indicates if the chat is muted or not

@@ -670,7 +680,7 @@

unpin diff --git a/docs/authStrategies_BaseAuthStrategy.js.html b/docs/authStrategies_BaseAuthStrategy.js.html index d3b69d88a51..5524fb87e21 100644 --- a/docs/authStrategies_BaseAuthStrategy.js.html +++ b/docs/authStrategies_BaseAuthStrategy.js.html @@ -45,7 +45,7 @@

Source: authStrategies/BaseAuthStrategy.js

return { failed: false, restart: false, - failureEventPayload: undefined + failureEventPayload: undefined, }; } async getAuthEventPayload() {} @@ -55,7 +55,8 @@

Source: authStrategies/BaseAuthStrategy.js

async logout() {} } -module.exports = BaseAuthStrategy;
+module.exports = BaseAuthStrategy; +

@@ -65,7 +66,7 @@

Source: authStrategies/BaseAuthStrategy.js

diff --git a/docs/authStrategies_LocalAuth.js.html b/docs/authStrategies_LocalAuth.js.html index de6b3f915cf..efe7da8bebb 100644 --- a/docs/authStrategies_LocalAuth.js.html +++ b/docs/authStrategies_LocalAuth.js.html @@ -39,16 +39,18 @@

Source: authStrategies/LocalAuth.js

* Local directory-based authentication * @param {object} options - options * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance - * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" + * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" * @param {number} options.rmMaxRetries - Sets the maximum number of retries for removing the session directory -*/ + */ class LocalAuth extends BaseAuthStrategy { - constructor({ clientId, dataPath, rmMaxRetries }={}) { + constructor({ clientId, dataPath, rmMaxRetries } = {}) { super(); const idRegex = /^[-_\w]+$/i; - if(clientId && !idRegex.test(clientId)) { - throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.'); + if (clientId && !idRegex.test(clientId)) { + throw new Error( + 'Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.', + ); } this.dataPath = path.resolve(dataPath || './.wwebjs_auth/'); @@ -58,18 +60,25 @@

Source: authStrategies/LocalAuth.js

async beforeBrowserInitialized() { const puppeteerOpts = this.client.options.puppeteer; - const sessionDirName = this.clientId ? `session-${this.clientId}` : 'session'; + const sessionDirName = this.clientId + ? `session-${this.clientId}` + : 'session'; const dirPath = path.join(this.dataPath, sessionDirName); - if(puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) { - throw new Error('LocalAuth is not compatible with a user-supplied userDataDir.'); + if ( + puppeteerOpts.userDataDir && + puppeteerOpts.userDataDir !== dirPath + ) { + throw new Error( + 'LocalAuth is not compatible with a user-supplied userDataDir.', + ); } fs.mkdirSync(dirPath, { recursive: true }); - + this.client.options.puppeteer = { ...puppeteerOpts, - userDataDir: dirPath + userDataDir: dirPath, }; this.userDataDir = dirPath; @@ -77,13 +86,17 @@

Source: authStrategies/LocalAuth.js

async logout() { if (this.userDataDir) { - await fs.promises.rm(this.userDataDir, { recursive: true, force: true, maxRetries: this.rmMaxRetries }) + await fs.promises + .rm(this.userDataDir, { + recursive: true, + force: true, + maxRetries: this.rmMaxRetries, + }) .catch((e) => { throw new Error(e); }); } } - } module.exports = LocalAuth; @@ -97,7 +110,7 @@

Source: authStrategies/LocalAuth.js

diff --git a/docs/authStrategies_NoAuth.js.html b/docs/authStrategies_NoAuth.js.html index b6b4699b4ec..39876f146bd 100644 --- a/docs/authStrategies_NoAuth.js.html +++ b/docs/authStrategies_NoAuth.js.html @@ -36,11 +36,11 @@

Source: authStrategies/NoAuth.js

/** * No session restoring functionality * Will need to authenticate via QR code every time -*/ -class NoAuth extends BaseAuthStrategy { } + */ +class NoAuth extends BaseAuthStrategy {} - -module.exports = NoAuth; +module.exports = NoAuth; + @@ -50,7 +50,7 @@

Source: authStrategies/NoAuth.js

diff --git a/docs/authStrategies_RemoteAuth.js.html b/docs/authStrategies_RemoteAuth.js.html index e201391a8a8..34313a6c2ed 100644 --- a/docs/authStrategies_RemoteAuth.js.html +++ b/docs/authStrategies_RemoteAuth.js.html @@ -51,40 +51,64 @@

Source: authStrategies/RemoteAuth.js

* @param {object} options - options * @param {object} options.store - Remote database store instance * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance - * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" + * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" * @param {number} options.backupSyncIntervalMs - Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute} * @param {number} options.rmMaxRetries - Sets the maximum number of retries for removing the session directory */ class RemoteAuth extends BaseAuthStrategy { - constructor({ clientId, dataPath, store, backupSyncIntervalMs, rmMaxRetries } = {}) { - if (!fs && !unzipper && !archiver) throw new Error('Optional Dependencies [fs-extra, unzipper, archiver] are required to use RemoteAuth. Make sure to run npm install correctly and remove the --no-optional flag'); + constructor({ + clientId, + dataPath, + store, + backupSyncIntervalMs, + rmMaxRetries, + } = {}) { + if (!fs && !unzipper && !archiver) + throw new Error( + 'Optional Dependencies [fs-extra, unzipper, archiver] are required to use RemoteAuth. Make sure to run npm install correctly and remove the --no-optional flag', + ); super(); const idRegex = /^[-_\w]+$/i; if (clientId && !idRegex.test(clientId)) { - throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.'); + throw new Error( + 'Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.', + ); } if (!backupSyncIntervalMs || backupSyncIntervalMs < 60000) { - throw new Error('Invalid backupSyncIntervalMs. Accepts values starting from 60000ms {1 minute}.'); + throw new Error( + 'Invalid backupSyncIntervalMs. Accepts values starting from 60000ms {1 minute}.', + ); } - if(!store) throw new Error('Remote database store is required.'); + if (!store) throw new Error('Remote database store is required.'); this.store = store; this.clientId = clientId; this.backupSyncIntervalMs = backupSyncIntervalMs; this.dataPath = path.resolve(dataPath || './.wwebjs_auth/'); this.tempDir = `${this.dataPath}/wwebjs_temp_session_${this.clientId}`; - this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files & Dirs in WWebJS to restore session */ + this.requiredDirs = [ + 'Default', + 'IndexedDB', + 'Local Storage', + ]; /* => Required Files & Dirs in WWebJS to restore session */ this.rmMaxRetries = rmMaxRetries ?? 4; } async beforeBrowserInitialized() { const puppeteerOpts = this.client.options.puppeteer; - const sessionDirName = this.clientId ? `RemoteAuth-${this.clientId}` : 'RemoteAuth'; + const sessionDirName = this.clientId + ? `RemoteAuth-${this.clientId}` + : 'RemoteAuth'; const dirPath = path.join(this.dataPath, sessionDirName); - if (puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) { - throw new Error('RemoteAuth is not compatible with a user-supplied userDataDir.'); + if ( + puppeteerOpts.userDataDir && + puppeteerOpts.userDataDir !== dirPath + ) { + throw new Error( + 'RemoteAuth is not compatible with a user-supplied userDataDir.', + ); } this.userDataDir = dirPath; @@ -94,7 +118,7 @@

Source: authStrategies/RemoteAuth.js

this.client.options.puppeteer = { ...puppeteerOpts, - userDataDir: dirPath + userDataDir: dirPath, }; } @@ -111,20 +135,26 @@

Source: authStrategies/RemoteAuth.js

let pathExists = await this.isValidPath(this.userDataDir); if (pathExists) { - await fs.promises.rm(this.userDataDir, { - recursive: true, - force: true, - maxRetries: this.rmMaxRetries, - }).catch(() => {}); + await fs.promises + .rm(this.userDataDir, { + recursive: true, + force: true, + maxRetries: this.rmMaxRetries, + }) + .catch(() => {}); } clearInterval(this.backupSync); } async afterAuthReady() { - const sessionExists = await this.store.sessionExists({session: this.sessionName}); - if(!sessionExists) { - await this.delay(60000); /* Initial delay sync required for session to be stable enough to recover */ - await this.storeRemoteSession({emit: true}); + const sessionExists = await this.store.sessionExists({ + session: this.sessionName, + }); + if (!sessionExists) { + await this.delay( + 60000, + ); /* Initial delay sync required for session to be stable enough to recover */ + await this.storeRemoteSession({ emit: true }); } var self = this; this.backupSync = setInterval(async function () { @@ -133,34 +163,57 @@

Source: authStrategies/RemoteAuth.js

} async storeRemoteSession(options) { - /* Compress & Store Session */ const pathExists = await this.isValidPath(this.userDataDir); - if (pathExists) { - await this.compressSession(); - await this.store.save({session: path.join(this.dataPath, this.sessionName)}); - await fs.promises.unlink(path.join(this.dataPath, `${this.sessionName}.zip`)); - await fs.promises.rm(`${this.tempDir}`, { - recursive: true, - force: true, - maxRetries: this.rmMaxRetries, - }).catch(() => {}); - if(options && options.emit) this.client.emit(Events.REMOTE_SESSION_SAVED); + if (!pathExists) return; + + let compressedSessionPath; + try { + compressedSessionPath = await this.compressSession(); + await this.store.save({ + session: this.sessionName, + }); + if (options && options.emit) + this.client.emit(Events.REMOTE_SESSION_SAVED); + } finally { + const paths = [ + this.tempDir, + ...(compressedSessionPath ? [compressedSessionPath] : []), + ]; + await Promise.allSettled( + paths.map((p) => + fs.promises.rm(p, { + recursive: true, + force: true, + maxRetries: this.rmMaxRetries, + }), + ), + ); } } async extractRemoteSession() { const pathExists = await this.isValidPath(this.userDataDir); - const compressedSessionPath = path.join(this.dataPath, `${this.sessionName}.zip`); - const sessionExists = await this.store.sessionExists({session: this.sessionName}); + const compressedSessionPath = path.join( + this.dataPath, + `${this.sessionName}.zip`, + ); + const sessionExists = await this.store.sessionExists({ + session: this.sessionName, + }); if (pathExists) { - await fs.promises.rm(this.userDataDir, { - recursive: true, - force: true, - maxRetries: this.rmMaxRetries, - }).catch(() => {}); + await fs.promises + .rm(this.userDataDir, { + recursive: true, + force: true, + maxRetries: this.rmMaxRetries, + }) + .catch(() => {}); } if (sessionExists) { - await this.store.extract({session: this.sessionName, path: compressedSessionPath}); + await this.store.extract({ + session: this.sessionName, + path: compressedSessionPath, + }); await this.unCompressSession(compressedSessionPath); } else { fs.mkdirSync(this.userDataDir, { recursive: true }); @@ -168,59 +221,62 @@

Source: authStrategies/RemoteAuth.js

} async deleteRemoteSession() { - const sessionExists = await this.store.sessionExists({session: this.sessionName}); - if (sessionExists) await this.store.delete({session: this.sessionName}); + const sessionExists = await this.store.sessionExists({ + session: this.sessionName, + }); + if (sessionExists) + await this.store.delete({ session: this.sessionName }); } async compressSession() { + const stageDefaultPath = path.join(this.tempDir, 'Default'); + const userDataDefaultPath = path.join(this.userDataDir, 'Default'); + + await fs.emptyDir(stageDefaultPath); + await this.copyByRequiredDirs(userDataDefaultPath, stageDefaultPath); + const archive = archiver('zip'); - const stream = fs.createWriteStream(path.join(this.dataPath, `${this.sessionName}.zip`)); + const outPath = path.join(this.dataPath, `${this.sessionName}.zip`); + const out = fs.createWriteStream(outPath); - await fs.copy(this.userDataDir, this.tempDir).catch(() => {}); - await this.deleteMetadata(); - return new Promise((resolve, reject) => { - archive - .directory(this.tempDir, false) - .on('error', err => reject(err)) - .pipe(stream); + await new Promise((resolve, reject) => { + out.once('close', resolve); + out.once('error', reject); + archive.once('error', reject); - stream.on('close', () => resolve()); + archive.pipe(out); + archive.directory(this.tempDir, false); archive.finalize(); }); + return outPath; } async unCompressSession(compressedSessionPath) { var stream = fs.createReadStream(compressedSessionPath); await new Promise((resolve, reject) => { - stream.pipe(unzipper.Extract({ - path: this.userDataDir, - concurrency: 10 - })) - .on('error', err => reject(err)) + stream + .pipe( + unzipper.Extract({ + path: this.userDataDir, + concurrency: 10, + }), + ) + .on('error', (err) => reject(err)) .on('finish', () => resolve()); }); await fs.promises.unlink(compressedSessionPath); } - async deleteMetadata() { - const sessionDirs = [this.tempDir, path.join(this.tempDir, 'Default')]; - for (const dir of sessionDirs) { - const sessionFiles = await fs.promises.readdir(dir); - for (const element of sessionFiles) { - if (!this.requiredDirs.includes(element)) { - const dirElement = path.join(dir, element); - const stats = await fs.promises.lstat(dirElement); - - if (stats.isDirectory()) { - await fs.promises.rm(dirElement, { - recursive: true, - force: true, - maxRetries: this.rmMaxRetries, - }).catch(() => {}); - } else { - await fs.promises.unlink(dirElement).catch(() => {}); - } - } + async copyByRequiredDirs(from, to) { + for (const d of this.requiredDirs) { + const src = path.join(from, d); + if (await this.isValidPath(src)) { + const dest = path.join(to, path.basename(src)); + await fs.promises.cp(src, dest, { + recursive: true, + force: true, + errorOnExist: false, + }); } } } @@ -235,7 +291,7 @@

Source: authStrategies/RemoteAuth.js

} async delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } } @@ -250,7 +306,7 @@

Source: authStrategies/RemoteAuth.js

diff --git a/docs/global.html b/docs/global.html index 0019e9e05b5..4e717df8560 100644 --- a/docs/global.html +++ b/docs/global.html @@ -188,9 +188,6 @@

Abstract types

TargetOptions
-
TargetOptions
-
-
TransferChannelOwnershipOptions
@@ -375,6 +372,19 @@

Properties

+ + +

MESSAGE_CIPHERTEXT_FAILED

+ + +

 

+ + +

 

+ + + +

MESSAGE_CREATE

@@ -4163,68 +4173,6 @@

Properties

TargetOptions  Object

Target options object description

-
-

Properties

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

module

-
-

(string or number)

-
-

 

-
-

The name or a key of the target module to search

-
-

index

-
-

number

-
-

 

-
-

The index value of the target module

-
-

function

-
-

string

-
-

 

-
-

The function name to get from a module

-
-
-
-
-

TargetOptions -  Object

-

Target options object description

Properties

@@ -4385,7 +4333,7 @@

Property

diff --git a/docs/index.html b/docs/index.html index 68a76fb3d9b..46d5ceb58e1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -31,56 +31,57 @@

-

- WWebJS Website + + WWebJS Website +

-

- npm - Depfu - WhatsApp_Web 2.2346.52 - Discord server -

-
+ npm + NPM Downloads + GitHub contributors + Depfu + Discord server +

About

-

A WhatsApp API client that operates via the WhatsApp Web browser.

-

The library launches the WhatsApp Web browser app via Puppeteer, accessing its internal functions and creating a managed instance to reduce the risk of being blocked. This gives the API client nearly all WhatsApp Web features for dynamic use in a 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.

-
+

whatsapp‑web.js is a powerful Node.js library that lets you interact with WhatsApp Web, making it easy to build a dynamic WhatsApp API with nearly all features of the web client. It uses Puppeteer to access WhatsApp Web’s internal functions and runs them in a managed browser instance to reduce the risk of being blocked.

Links

Installation

-

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

-
-

[!NOTE] -Node v18 or higher, is required.
-See the Guide for quick upgrade instructions.

-
+

Node.js v18.0.0 or higher, is required.

+
npm install whatsapp-web.js
+yarn add whatsapp-web.js
+pnpm add whatsapp-web.js
+
+

Having trouble installing? Take a peak at the Guide for more detailed instructions.

Example usage

const { Client } = require('whatsapp-web.js');
+const qrcode = require('qrcode-terminal');
 
 const client = new Client();
 
 client.on('qr', (qr) => {
-    // Generate and scan this code with your phone
-    console.log('QR RECEIVED', qr);
+    qrcode.generate(qr, { small: true });
 });
 
 client.on('ready', () => {
     console.log('Client is ready!');
 });
 
-client.on('message', msg => {
+client.on('message', (msg) => {
     if (msg.body == '!ping') {
         msg.reply('pong');
     }
@@ -88,8 +89,8 @@ 

Example usage

client.initialize();
-

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.

+

Take a look at example.js for additional examples and use cases.
+For more details on saving and restoring sessions, check out the Authentication Strategies.

Supported features

@@ -137,11 +138,11 @@

Supported features

- + - + @@ -221,7 +222,7 @@

Supported features

- + @@ -230,22 +231,21 @@

Supported features

Send buttons(DEPRECATED)(DEPRECATED)
Send lists(DEPRECATED)(DEPRECATED)
Receive location
Vote in polls🔜
Communities

Something missing? Make an issue and let us know!

-

Contributing

-

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

+

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

+

Contributing

+

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!

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 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.
-You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

+You may obtain a copy of the License at https://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.
@@ -898,6 +898,11 @@

Chat

+
+ Chat#isLocked +
+
+
Chat#isMuted
@@ -938,16 +943,16 @@

Chat

-
- Chat#pinned -
-
-
+
+ Chat#pinned +
+
+
Chat#sendMessage(content[, options])
@@ -1097,6 +1102,11 @@

Client

+
+ Client#cancelPairingCode() +
+
+
Client#createCallLink(startTime, callType)
@@ -1233,7 +1243,7 @@

Client

- Client#event:message_create + Client#event:message_ciphertext_failed
@@ -1242,6 +1252,11 @@

Client

- - + +
+
Events.GROUP_MEMBERSHIP_REQUEST
@@ -1918,6 +1938,11 @@

Events

+
+ Events.MESSAGE_CIPHERTEXT_FAILED +
+
+
Events.MESSAGE_CREATE
@@ -1928,16 +1953,16 @@

Events

-
-
- - + +
+
Events.MESSAGE_RECEIVED
@@ -2115,6 +2140,11 @@

GroupChat

+
+ GroupChat#isLocked +
+
+
GroupChat#isMuted
@@ -2185,16 +2215,16 @@

GroupChat

-
- GroupChat#removeParticipants(participantIds) -
-
-
+
+ GroupChat#removeParticipants(participantIds) +
+
+
GroupChat#revokeInvite()
@@ -3671,6 +3701,11 @@

PrivateChat

+
+ PrivateChat#isLocked +
+
+
PrivateChat#isMuted
@@ -3711,16 +3746,16 @@

PrivateChat

-
- PrivateChat#pinned -
-
-
+
+ PrivateChat#pinned +
+
+
PrivateChat#sendMessage(content[, options])
@@ -4391,7 +4426,7 @@

window

- window.compareWwebVersions(lOperand, operator, rOperand) + window.WWebJS.compareWwebVersions(lOperand, operator, rOperand)
@@ -4401,7 +4436,7 @@

window

- window.injectToFunction(target, callback) + window.WWebJS.injectToFunction(target, callback)
@@ -4426,7 +4461,7 @@

window

diff --git a/docs/structures_Base.js.html b/docs/structures_Base.js.html index f60a2ecc6d9..cd121911fe6 100644 --- a/docs/structures_Base.js.html +++ b/docs/structures_Base.js.html @@ -46,11 +46,14 @@

Source: structures/Base.js

_clone() { return Object.assign(Object.create(this), this); } - - _patch(data) { return data; } + + _patch(data) { + return data; + } } -module.exports = Base; +module.exports = Base; +
@@ -60,7 +63,7 @@

Source: structures/Base.js

diff --git a/docs/structures_Broadcast.js.html b/docs/structures_Broadcast.js.html index 5fb7b3c5368..40b71d4db1a 100644 --- a/docs/structures_Broadcast.js.html +++ b/docs/structures_Broadcast.js.html @@ -74,7 +74,7 @@

Source: structures/Broadcast.js

* Messages statuses * @type {Message[]} */ - this.msgs = data.msgs?.map(msg => new Message(this.client, msg)); + this.msgs = data.msgs?.map((msg) => new Message(this.client, msg)); return super._patch(data); } @@ -94,7 +94,6 @@

Source: structures/Broadcast.js

getContact() { return this.client.getContactById(this.id._serialized); } - } module.exports = Broadcast; @@ -108,7 +107,7 @@

Source: structures/Broadcast.js

diff --git a/docs/structures_BusinessContact.js.html b/docs/structures_BusinessContact.js.html index 972d39253ce..6f180e580ec 100644 --- a/docs/structures_BusinessContact.js.html +++ b/docs/structures_BusinessContact.js.html @@ -46,10 +46,10 @@

Source: structures/BusinessContact.js

return super._patch(data); } - } -module.exports = BusinessContact; +module.exports = BusinessContact; +
@@ -59,7 +59,7 @@

Source: structures/BusinessContact.js

diff --git a/docs/structures_Buttons.js.html b/docs/structures_Buttons.js.html index 4c64918a67a..6bfde378c33 100644 --- a/docs/structures_Buttons.js.html +++ b/docs/structures_Buttons.js.html @@ -70,7 +70,7 @@

Source: structures/Buttons.js

* @type {string} */ this.title = title; - + /** * footer of message * @type {string} @@ -80,7 +80,7 @@

Source: structures/Buttons.js

if (body instanceof MessageMedia) { this.type = 'media'; this.title = ''; - }else{ + } else { this.type = 'chat'; } @@ -89,28 +89,33 @@

Source: structures/Buttons.js

* @type {FormattedButtonSpec[]} */ this.buttons = this._format(buttons); - if(!this.buttons.length){ throw '[BT01] No buttons';} - + if (!this.buttons.length) { + throw '[BT01] No buttons'; + } } /** * Creates button array from simple array * @param {ButtonSpec[]} buttons * @returns {FormattedButtonSpec[]} - * @example + * @example * Input: [{id:'customId',body:'button1'},{body:'button2'},{body:'button3'},{body:'button4'}] * Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}] */ - _format(buttons){ - buttons = buttons.slice(0,3); // phone users can only see 3 buttons, so lets limit this + _format(buttons) { + buttons = buttons.slice(0, 3); // phone users can only see 3 buttons, so lets limit this return buttons.map((btn) => { - return {'buttonId':btn.id ? String(btn.id) : Util.generateHash(6),'buttonText':{'displayText':btn.body},'type':1}; + return { + buttonId: btn.id ? String(btn.id) : Util.generateHash(6), + buttonText: { displayText: btn.body }, + type: 1, + }; }); } - } -module.exports = Buttons; +module.exports = Buttons; + @@ -120,7 +125,7 @@

Source: structures/Buttons.js

diff --git a/docs/structures_Call.js.html b/docs/structures_Call.js.html index 82626a04349..b5f0b8a0d6d 100644 --- a/docs/structures_Call.js.html +++ b/docs/structures_Call.js.html @@ -90,21 +90,26 @@

Source: structures/Call.js

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

Source: structures/Call.js

diff --git a/docs/structures_Channel.js.html b/docs/structures_Channel.js.html index 0d519376397..622947cf289 100644 --- a/docs/structures_Channel.js.html +++ b/docs/structures_Channel.js.html @@ -68,11 +68,12 @@

Source: structures/Channel.js

*/ this.name = data.name; - /** + /** * The channel description * @type {string} */ - this.description = data.channelMetadata.description; + this.description = + data.channelMetadata?.description ?? data.description ?? ''; /** * Indicates if it is a Channel @@ -120,7 +121,9 @@

Source: structures/Channel.js

* Last message in the channel * @type {Message} */ - this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined; + this.lastMessage = data.lastMessage + ? new Message(super.client, data.lastMessage) + : undefined; return super._patch(data); } @@ -131,58 +134,82 @@

Source: structures/Channel.js

* @returns {Promise<Array<{contact: Contact, role: string}>>} Returns an array of objects that handle the subscribed contacts and their roles in the channel */ async getSubscribers(limit) { - return await this.client.pupPage.evaluate(async (channelId, limit) => { - const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); - if (!channel) return []; - !limit && (limit = window.Store.ChannelUtils.getMaxSubscriberNumber()); - const response = await window.Store.ChannelSubscribers.mexFetchNewsletterSubscribers(channelId, limit); - const contacts = window.Store.ChannelSubscribers.getSubscribersInContacts(response.subscribers); - return Promise.all(contacts.map((obj) => ({ - ...obj, - contact: window.WWebJS.getContactModel(obj.contact) - }))); - }, this.id._serialized, limit); + return await this.client.pupPage.evaluate( + async (channelId, limit) => { + const channel = await window.WWebJS.getChat(channelId, { + getAsModel: false, + }); + if (!channel) return []; + !limit && + (limit = window + .require('WAWebNewsletterGatingUtils') + .getMaxSubscriberNumber()); + const response = await window + .require('WAWebMexFetchNewsletterSubscribersJob') + .mexFetchNewsletterSubscribers(channelId, limit); + const contacts = window + .require('WAWebNewsletterSubscriberListAction') + .getSubscribersInContacts(response.subscribers); + return Promise.all( + contacts.map((obj) => ({ + ...obj, + contact: window.WWebJS.getContactModel(obj.contact), + })), + ); + }, + this.id._serialized, + limit, + ); } /** * Updates the channel subject - * @param {string} newSubject + * @param {string} newSubject * @returns {Promise<boolean>} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions. */ async setSubject(newSubject) { - const success = await this._setChannelMetadata({ name: newSubject }, { editName: true }); + const success = await this._setChannelMetadata( + { name: newSubject }, + { editName: true }, + ); success && (this.name = newSubject); return success; } /** * Updates the channel description - * @param {string} newDescription + * @param {string} newDescription * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async setDescription(newDescription) { - const success = await this._setChannelMetadata({ description: newDescription }, { editDescription: true }); + const success = await this._setChannelMetadata( + { description: newDescription }, + { editDescription: true }, + ); success && (this.description = newDescription); return success; } /** * Updates the channel profile picture - * @param {MessageMedia} newProfilePicture + * @param {MessageMedia} newProfilePicture * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async setProfilePicture(newProfilePicture) { - return await this._setChannelMetadata({ picture: newProfilePicture }, { editPicture: true }); + return await this._setChannelMetadata( + { picture: newProfilePicture }, + { editPicture: true }, + ); } /** * Updates available reactions to use in the channel - * + * * Valid values for passing to the method are: * 0 for NONE reactions to be avaliable * 1 for BASIC reactions to be available: 👍, ❤️, 😂, 😮, 😢, 🙏 * 2 for ALL reactions to be available - * @param {number} reactionCode + * @param {number} reactionCode * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async setReactionSetting(reactionCode) { @@ -190,11 +217,11 @@

Source: structures/Channel.js

const reactionMapper = { 0: 3, 1: 1, - 2: 0 + 2: 0, }; const success = await this._setChannelMetadata( { reactionCodesSetting: reactionMapper[reactionCode] }, - { editReactionCodesSetting: true } + { editReactionCodesSetting: true }, ); success && (this.channelMetadata.reactionCodesSetting = reactionCode); return success; @@ -212,7 +239,7 @@

Source: structures/Channel.js

} return success; } - + /** * Unmutes the channel * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise @@ -260,11 +287,15 @@

Source: structures/Channel.js

/** * Sends a channel admin invitation to a user, allowing them to become an admin of the channel * @param {string} chatId The ID of a user to send the channel admin invitation to - * @param {SendChannelAdminInviteOptions} options + * @param {SendChannelAdminInviteOptions} options * @returns {Promise<boolean>} Returns true if an invitation was sent successfully, false otherwise */ async sendChannelAdminInvite(chatId, options = {}) { - return this.client.sendChannelAdminInvite(chatId, this.id._serialized, options); + return this.client.sendChannelAdminInvite( + chatId, + this.id._serialized, + options, + ); } /** @@ -281,7 +312,10 @@

Source: structures/Channel.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async revokeChannelAdminInvite(userId) { - return this.client.revokeChannelAdminInvite(this.id._serialized, userId); + return this.client.revokeChannelAdminInvite( + this.id._serialized, + userId, + ); } /** @@ -307,7 +341,11 @@

Source: structures/Channel.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async transferChannelOwnership(newOwnerId, options = {}) { - return this.client.transferChannelOwnership(this.id._serialized, newOwnerId, options); + return this.client.transferChannelOwnership( + this.id._serialized, + newOwnerId, + options, + ); } /** @@ -318,36 +356,50 @@

Source: structures/Channel.js

* @returns {Promise<Array<Message>>} */ async fetchMessages(searchOptions) { - let messages = await this.client.pupPage.evaluate(async (channelId, searchOptions) => { - const msgFilter = (m) => { - if (m.isNotification || m.type === 'newsletter_notification') { - return false; // dont include notification messages - } - if (searchOptions && searchOptions.fromMe !== undefined && m.id.fromMe !== searchOptions.fromMe) { - return false; + let messages = await this.client.pupPage.evaluate( + async (channelId, searchOptions) => { + const msgFilter = (m) => { + if ( + m.isNotification || + m.type === 'newsletter_notification' + ) { + return false; // dont include notification messages + } + if ( + searchOptions && + searchOptions.fromMe !== undefined && + m.id.fromMe !== searchOptions.fromMe + ) { + return false; + } + return true; + }; + + const channel = await window.WWebJS.getChat(channelId, { + getAsModel: false, + }); + let msgs = channel.msgs.getModelsArray().filter(msgFilter); + + if (searchOptions && searchOptions.limit > 0) { + while (msgs.length < searchOptions.limit) { + const loadedMessages = await window + .require('WAWebChatLoadMessages') + .loadEarlierMsgs(channel); + if (!loadedMessages || !loadedMessages.length) break; + msgs = [...loadedMessages.filter(msgFilter), ...msgs]; + } + + if (msgs.length > searchOptions.limit) { + msgs.sort((a, b) => (a.t > b.t ? 1 : -1)); + msgs = msgs.splice(msgs.length - searchOptions.limit); + } } - return true; - }; - const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); - let msgs = channel.msgs.getModelsArray().filter(msgFilter); - - if (searchOptions && searchOptions.limit > 0) { - while (msgs.length < searchOptions.limit) { - const loadedMessages = await window.Store.ConversationMsgs.loadEarlierMsgs(channel); - if (!loadedMessages || !loadedMessages.length) break; - msgs = [...loadedMessages.filter(msgFilter), ...msgs]; - } - - if (msgs.length > searchOptions.limit) { - msgs.sort((a, b) => (a.t > b.t) ? 1 : -1); - msgs = msgs.splice(msgs.length - searchOptions.limit); - } - } - - return msgs.map(m => window.WWebJS.getMessageModel(m)); - - }, this.id._serialized, searchOptions); + return msgs.map((m) => window.WWebJS.getMessageModel(m)); + }, + this.id._serialized, + searchOptions, + ); return messages.map((msg) => new Message(this.client, msg)); } @@ -367,27 +419,39 @@

Source: structures/Channel.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async _setChannelMetadata(value, property) { - return await this.client.pupPage.evaluate(async (channelId, value, property) => { - const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); - if (!channel) return false; - if (property.editPicture) { - value.picture = value.picture - ? await window.WWebJS.cropAndResizeImage(value.picture, { - asDataUrl: true, - mimetype: 'image/jpeg', - size: 640, - quality: 1 - }) - : null; - } - try { - await window.Store.ChannelUtils.editNewsletterMetadataAction(channel, property, value); - return true; - } catch (err) { - if (err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, this.id._serialized, value, property); + return await this.client.pupPage.evaluate( + async (channelId, value, property) => { + const channel = await window.WWebJS.getChat(channelId, { + getAsModel: false, + }); + if (!channel) return false; + if (property.editPicture) { + value.picture = value.picture + ? await window.WWebJS.cropAndResizeImage( + value.picture, + { + asDataUrl: true, + mimetype: 'image/jpeg', + size: 640, + quality: 1, + }, + ) + : null; + } + try { + await window + .require('WAWebEditNewsletterMetadataAction') + .editNewsletterMetadataAction(channel, property, value); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + this.id._serialized, + value, + property, + ); } /** @@ -396,17 +460,35 @@

Source: structures/Channel.js

* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise */ async _muteUnmuteChannel(action) { - return await this.client.pupPage.evaluate(async (channelId, action) => { - try { - action === 'MUTE' - ? await window.Store.ChannelUtils.muteNewsletter([channelId]) - : await window.Store.ChannelUtils.unmuteNewsletter([channelId]); - return true; - } catch (err) { - if (err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, this.id._serialized, action); + return await this.client.pupPage.evaluate( + async (channelId, action) => { + try { + await window + .require('WAWebNewsletterUpdateUserSettingJob') + .updateNewsletterUserSetting({ + newsletterJid: window + .require('WAJids') + .toNewsletterJid(channelId), + type: window.require('WAWebNewsletterModelUtils') + .ADMIN_NOTIFICATIONS, + muteExpirationValue: + action === 'MUTE' + ? window.require( + 'WAWebNewsletterModelUtils', + ).MUTED_STATE + : window.require( + 'WAWebNewsletterModelUtils', + ).UNMUTED_STATE, + }); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + this.id._serialized, + action, + ); } } @@ -421,7 +503,7 @@

Source: structures/Channel.js

diff --git a/docs/structures_Chat.js.html b/docs/structures_Chat.js.html index fddbf81360a..a2150a5f7c3 100644 --- a/docs/structures_Chat.js.html +++ b/docs/structures_Chat.js.html @@ -94,6 +94,12 @@

Source: structures/Chat.js

*/ this.pinned = !!data.pin; + /** + * Indicates if the Chat is locked + * @type {boolean} + */ + this.isLocked = data.isLocked; + /** * Indicates if the chat is muted or not * @type {boolean} @@ -110,15 +116,17 @@

Source: structures/Chat.js

* Last message fo chat * @type {Message} */ - this.lastMessage = data.lastMessage ? new Message(this.client, data.lastMessage) : undefined; - + this.lastMessage = data.lastMessage + ? new Message(this.client, data.lastMessage) + : undefined; + return super._patch(data); } /** * Send a message to this chat * @param {string|MessageMedia|Location} content - * @param {MessageSendOptions} [options] + * @param {MessageSendOptions} [options] * @returns {Promise<Message>} Message that was just sent */ async sendMessage(content, options) { @@ -138,7 +146,7 @@

Source: structures/Chat.js

* @returns {Promise<boolean>} result */ async clearMessages() { - return this.client.pupPage.evaluate(chatId => { + return this.client.pupPage.evaluate((chatId) => { return window.WWebJS.sendClearChat(chatId); }, this.id._serialized); } @@ -148,7 +156,7 @@

Source: structures/Chat.js

* @returns {Promise<Boolean>} result */ async delete() { - return this.client.pupPage.evaluate(chatId => { + return this.client.pupPage.evaluate((chatId) => { return window.WWebJS.sendDeleteChat(chatId); }, this.id._serialized); } @@ -189,7 +197,10 @@

Source: structures/Chat.js

* @returns {Promise<{isMuted: boolean, muteExpiration: number}>} */ async mute(unmuteDate) { - const result = await this.client.muteChat(this.id._serialized, unmuteDate); + const result = await this.client.muteChat( + this.id._serialized, + unmuteDate, + ); this.isMuted = result.isMuted; this.muteExpiration = result.muteExpiration; return result; @@ -209,7 +220,7 @@

Source: structures/Chat.js

/** * Mark this chat as unread */ - async markUnread(){ + async markUnread() { return this.client.markChatUnread(this.id._serialized); } @@ -221,45 +232,56 @@

Source: structures/Chat.js

* @returns {Promise<Array<Message>>} */ async fetchMessages(searchOptions) { - let messages = await this.client.pupPage.evaluate(async (chatId, searchOptions) => { - const msgFilter = (m) => { - if (m.isNotification) { - return false; // dont include notification messages - } - if (searchOptions && searchOptions.fromMe !== undefined && m.id.fromMe !== searchOptions.fromMe) { - return false; + let messages = await this.client.pupPage.evaluate( + async (chatId, searchOptions) => { + const msgFilter = (m) => { + if (m.isNotification) { + return false; // dont include notification messages + } + if ( + searchOptions && + searchOptions.fromMe !== undefined && + m.id.fromMe !== searchOptions.fromMe + ) { + return false; + } + return true; + }; + + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + let msgs = chat.msgs.getModelsArray().filter(msgFilter); + + if (searchOptions && searchOptions.limit > 0) { + while (msgs.length < searchOptions.limit) { + const loadedMessages = await window + .require('WAWebChatLoadMessages') + .loadEarlierMsgs(chat, chat.msgs); + if (!loadedMessages || !loadedMessages.length) break; + msgs = [...loadedMessages.filter(msgFilter), ...msgs]; + } + + if (msgs.length > searchOptions.limit) { + msgs.sort((a, b) => (a.t > b.t ? 1 : -1)); + msgs = msgs.splice(msgs.length - searchOptions.limit); + } } - return true; - }; - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - let msgs = chat.msgs.getModelsArray().filter(msgFilter); + return msgs.map((m) => window.WWebJS.getMessageModel(m)); + }, + this.id._serialized, + searchOptions, + ); - if (searchOptions && searchOptions.limit > 0) { - while (msgs.length < searchOptions.limit) { - const loadedMessages = await window.Store.ConversationMsgs.loadEarlierMsgs(chat,chat.msgs); - if (!loadedMessages || !loadedMessages.length) break; - msgs = [...loadedMessages.filter(msgFilter), ...msgs]; - } - - if (msgs.length > searchOptions.limit) { - msgs.sort((a, b) => (a.t > b.t) ? 1 : -1); - msgs = msgs.splice(msgs.length - searchOptions.limit); - } - } - - return msgs.map(m => window.WWebJS.getMessageModel(m)); - - }, this.id._serialized, searchOptions); - - return messages.map(m => new Message(this.client, m)); + return messages.map((m) => new Message(this.client, m)); } /** * Simulate typing in chat. This will last for 25 seconds. */ async sendStateTyping() { - return this.client.pupPage.evaluate(chatId => { + return this.client.pupPage.evaluate((chatId) => { window.WWebJS.sendChatstate('typing', chatId); return true; }, this.id._serialized); @@ -269,7 +291,7 @@

Source: structures/Chat.js

* Simulate recording audio in chat. This will last for 25 seconds. */ async sendStateRecording() { - return this.client.pupPage.evaluate(chatId => { + return this.client.pupPage.evaluate((chatId) => { window.WWebJS.sendChatstate('recording', chatId); return true; }, this.id._serialized); @@ -279,7 +301,7 @@

Source: structures/Chat.js

* Stops typing or recording in chat immediately. */ async clearState() { - return this.client.pupPage.evaluate(chatId => { + return this.client.pupPage.evaluate((chatId) => { window.WWebJS.sendChatstate('stop', chatId); return true; }, this.id._serialized); @@ -317,7 +339,7 @@

Source: structures/Chat.js

async getPinnedMessages() { return this.client.getPinnedMessages(this.id._serialized); } - + /** * Sync chat history conversation * @return {Promise<boolean>} True if operation completed successfully, false otherwise. @@ -352,7 +374,7 @@

Source: structures/Chat.js

*/ async getCustomerNote() { if (this.isGroup || this.isChannel) return null; - + return this.client.getCustomerNote(this.id._serialized); } } @@ -368,7 +390,7 @@

Source: structures/Chat.js

diff --git a/docs/structures_ClientInfo.js.html b/docs/structures_ClientInfo.js.html index c50df995633..3e57cfaaa98 100644 --- a/docs/structures_ClientInfo.js.html +++ b/docs/structures_ClientInfo.js.html @@ -93,13 +93,14 @@

Source: structures/ClientInfo.js

*/ async getBatteryStatus() { return await this.client.pupPage.evaluate(() => { - const { battery, plugged } = window.Store.Conn; + const { battery, plugged } = window.require('WAWebConnModel').Conn; return { battery, plugged }; }); } } -module.exports = ClientInfo; +module.exports = ClientInfo; + @@ -109,7 +110,7 @@

Source: structures/ClientInfo.js

diff --git a/docs/structures_Contact.js.html b/docs/structures_Contact.js.html index 1eae943a646..c6c2a9082f4 100644 --- a/docs/structures_Contact.js.html +++ b/docs/structures_Contact.js.html @@ -49,7 +49,7 @@

Source: structures/Contact.js

constructor(client, data) { super(client); - if(data) this._patch(data); + if (data) this._patch(data); } _patch(data) { @@ -139,7 +139,7 @@

Source: structures/Contact.js

* @type {boolean} */ this.isBlocked = data.isBlocked; - + return super._patch(data); } @@ -158,7 +158,7 @@

Source: structures/Contact.js

async getFormattedNumber() { return await this.client.getFormattedNumber(this.id._serialized); } - + /** * Returns the contact's countrycode, (1541859685@c.us) => (1) * @returns {Promise<string>} @@ -166,14 +166,14 @@

Source: structures/Contact.js

async getCountryCode() { return await this.client.getCountryCode(this.id._serialized); } - + /** - * Returns the Chat that corresponds to this Contact. + * Returns the Chat that corresponds to this Contact. * Will return null when getting chat for currently logged in user. * @returns {Promise<Chat>} */ async getChat() { - if(this.isMe) return null; + if (this.isMe) return null; return await this.client.getChatById(this.id._serialized); } @@ -183,11 +183,21 @@

Source: structures/Contact.js

* @returns {Promise<boolean>} */ async block() { - if(this.isGroup) return false; + if (this.isGroup) return false; await this.client.pupPage.evaluate(async (contactId) => { - const contact = window.Store.Contact.get(contactId); - await window.Store.BlockContact.blockContact({contact}); + const contact = await window + .require('WAWebCollections') + .Contact.find(contactId); + const resolved = window + .require('WAWebBlockContactUtils') + .getContactToBlockOnlyUseIfNoAssociatedChat( + contact, + 'ChatListBlock', + ); + await window + .require('WAWebBlockContactAction') + .blockContact({ contact: resolved }); }, this.id._serialized); this.isBlocked = true; @@ -199,11 +209,21 @@

Source: structures/Contact.js

* @returns {Promise<boolean>} */ async unblock() { - if(this.isGroup) return false; + if (this.isGroup) return false; await this.client.pupPage.evaluate(async (contactId) => { - const contact = window.Store.Contact.get(contactId); - await window.Store.BlockContact.unblockContact(contact); + const contact = await window + .require('WAWebCollections') + .Contact.find(contactId); + const resolved = window + .require('WAWebBlockContactUtils') + .getContactToBlockOnlyUseIfNoAssociatedChat( + contact, + 'ChatListBlock', + ); + await window + .require('WAWebBlockContactAction') + .unblockContact(resolved); }, this.id._serialized); this.isBlocked = false; @@ -216,12 +236,13 @@

Source: structures/Contact.js

*/ async getAbout() { const about = await this.client.pupPage.evaluate(async (contactId) => { - const wid = window.Store.WidFactory.createWid(contactId); - return window.Store.StatusUtils.getStatus({'token':'', 'wid': wid}); + const wid = window.require('WAWebWidFactory').createWid(contactId); + return window + .require('WAWebContactStatusBridge') + .getStatus({ token: '', wid: wid }); }, this.id._serialized); - if (typeof about.status !== 'string') - return null; + if (typeof about.status !== 'string') return null; return about.status; } @@ -237,7 +258,7 @@

Source: structures/Contact.js

/** * Gets the Contact's current status broadcast. * @returns {Promise<Broadcast>} - */ + */ async getBroadcast() { return await this.client.getBroadcastById(this.id._serialized); } @@ -254,7 +275,7 @@

Source: structures/Contact.js

diff --git a/docs/structures_GroupChat.js.html b/docs/structures_GroupChat.js.html index 7eee2bacf2e..3519c24f239 100644 --- a/docs/structures_GroupChat.js.html +++ b/docs/structures_GroupChat.js.html @@ -59,7 +59,7 @@

Source: structures/GroupChat.js

get owner() { return this.groupMetadata.owner; } - + /** * Gets the date at which the group was created * @type {date} @@ -68,7 +68,7 @@

Source: structures/GroupChat.js

return new Date(this.groupMetadata.creation * 1000); } - /** + /** * Gets the group description * @type {string} */ @@ -102,264 +102,440 @@

Source: structures/GroupChat.js

/** * Adds a list of participants by ID to the group - * @param {string|Array<string>} participantIds + * @param {string|Array<string>} participantIds * @param {AddParticipnatsOptions} options An object thay handles options for adding participants * @returns {Promise<Object.<string, AddParticipantsResult>|string>} Returns an object with the resulting data or an error message as a string */ async addParticipants(participantIds, options = {}) { - return await this.client.pupPage.evaluate(async (groupId, participantIds, options) => { - const { sleep = [250, 500], autoSendInviteV4 = true, comment = '' } = options; - const participantData = {}; - - !Array.isArray(participantIds) && (participantIds = [participantIds]); - const groupWid = window.Store.WidFactory.createWid(groupId); - const group = window.Store.Chat.get(groupWid) || (await window.Store.Chat.find(groupWid)); - const participantWids = participantIds.map((p) => window.Store.WidFactory.createWid(p)); - - const errorCodes = { - default: 'An unknown error occupied while adding a participant', - isGroupEmpty: 'AddParticipantsError: The participant can\'t be added to an empty group', - iAmNotAdmin: 'AddParticipantsError: You have no admin rights to add a participant to a group', - 200: 'The participant was added successfully', - 403: 'The participant can be added by sending private invitation only', - 404: 'The phone number is not registered on WhatsApp', - 408: 'You cannot add this participant because they recently left the group', - 409: 'The participant is already a group member', - 417: 'The participant can\'t be added to the community. You can invite them privately to join this group through its invite link', - 419: 'The participant can\'t be added because the group is full' - }; - - await window.Store.GroupQueryAndUpdate({ id: groupId }); - - let groupParticipants = group.groupMetadata?.participants.serialize(); - - if (!groupParticipants) { - return errorCodes.isGroupEmpty; - } + return await this.client.pupPage.evaluate( + async (groupId, participantIds, options) => { + const { + sleep = [250, 500], + autoSendInviteV4 = true, + comment = '', + } = options; + const participantData = {}; + + !Array.isArray(participantIds) && + (participantIds = [participantIds]); + const groupWid = window + .require('WAWebWidFactory') + .createWid(groupId); + const group = + window.require('WAWebCollections').Chat.get(groupWid) || + (await window + .require('WAWebCollections') + .Chat.find(groupWid)); + const participantWids = participantIds.map((p) => + window.require('WAWebWidFactory').createWid(p), + ); + + const errorCodes = { + default: + 'An unknown error occupied while adding a participant', + isGroupEmpty: + "AddParticipantsError: The participant can't be added to an empty group", + iAmNotAdmin: + 'AddParticipantsError: You have no admin rights to add a participant to a group', + 200: 'The participant was added successfully', + 403: 'The participant can be added by sending private invitation only', + 404: 'The phone number is not registered on WhatsApp', + 408: 'You cannot add this participant because they recently left the group', + 409: 'The participant is already a group member', + 417: "The participant can't be added to the community. You can invite them privately to join this group through its invite link", + 419: "The participant can't be added because the group is full", + }; - if (!group.iAmAdmin()) { - return errorCodes.iAmNotAdmin; - } + await window + .require('WAWebGroupQueryJob') + .queryAndUpdateGroupMetadataById({ id: groupId }); - groupParticipants.map(({ id }) => { - return id.server === 'lid' ? window.Store.LidUtils.getPhoneNumber(id) : id; - }); + let groupParticipants = + group.groupMetadata?.participants.serialize(); - const _getSleepTime = (sleep) => { - if (!Array.isArray(sleep) || sleep.length === 2 && sleep[0] === sleep[1]) { - return sleep; + if (!groupParticipants) { + return errorCodes.isGroupEmpty; } - if (sleep.length === 1) { - return sleep[0]; + + if (!group.iAmAdmin()) { + return errorCodes.iAmNotAdmin; } - (sleep[1] - sleep[0]) < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100); - return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0]; - }; - - for (let pWid of participantWids) { - const pId = pWid._serialized; - pWid = pWid.server === 'lid' ? window.Store.LidUtils.getPhoneNumber(pWid) : pWid; - - participantData[pId] = { - code: undefined, - message: undefined, - isInviteV4Sent: false + + groupParticipants.map(({ id }) => { + return id.server === 'lid' + ? window.require('WAWebApiContact').getPhoneNumber(id) + : id; + }); + + const _getSleepTime = (sleep) => { + if ( + !Array.isArray(sleep) || + (sleep.length === 2 && sleep[0] === sleep[1]) + ) { + return sleep; + } + if (sleep.length === 1) { + return sleep[0]; + } + sleep[1] - sleep[0] < 100 && + (sleep[0] = sleep[1]) && + (sleep[1] += 100); + return ( + Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + + sleep[0] + ); }; - if (groupParticipants.some(p => p._serialized === pId)) { - participantData[pId].code = 409; - participantData[pId].message = errorCodes[409]; - continue; - } + for (let pWid of participantWids) { + const pId = pWid._serialized; + pWid = + pWid.server === 'lid' + ? window + .require('WAWebApiContact') + .getPhoneNumber(pWid) + : pWid; + + participantData[pId] = { + code: undefined, + message: undefined, + isInviteV4Sent: false, + }; + + if (groupParticipants.some((p) => p._serialized === pId)) { + participantData[pId].code = 409; + participantData[pId].message = errorCodes[409]; + continue; + } - if (!(await window.Store.QueryExist(pWid))?.wid) { - participantData[pId].code = 404; - participantData[pId].message = errorCodes[404]; - continue; - } + if ( + !( + await window + .require('WAWebQueryExistsJob') + .queryWidExists(pWid) + )?.wid + ) { + participantData[pId].code = 404; + participantData[pId].message = errorCodes[404]; + continue; + } - const rpcResult = - await window.WWebJS.getAddParticipantsRpcResult(groupWid, pWid); - const { code: rpcResultCode } = rpcResult; - - participantData[pId].code = rpcResultCode; - participantData[pId].message = - errorCodes[rpcResultCode] || errorCodes.default; - - if (autoSendInviteV4 && rpcResultCode === 403) { - let userChat, isInviteV4Sent = false; - window.Store.Contact.gadd(pWid, { silent: true }); - - if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' && - (userChat = window.Store.Chat.get(pWid) || (await window.Store.Chat.find(pWid)))) { - const groupName = group.formattedTitle || group.name; - const res = await window.Store.GroupInviteV4.sendGroupInviteMessage( - userChat, - group.id._serialized, - groupName, - rpcResult.inviteV4Code, - rpcResult.inviteV4CodeExp, - comment, - await window.WWebJS.getProfilePicThumbToBase64(groupWid) + const rpcResult = + await window.WWebJS.getAddParticipantsRpcResult( + groupWid, + pWid, ); - isInviteV4Sent = res.messageSendResult === 'OK'; + const { code: rpcResultCode } = rpcResult; + + participantData[pId].code = rpcResultCode; + participantData[pId].message = + errorCodes[rpcResultCode] || errorCodes.default; + + if (autoSendInviteV4 && rpcResultCode === 403) { + let userChat, + isInviteV4Sent = false; + window + .require('WAWebCollections') + .Contact.gadd(pWid, { silent: true }); + + if ( + rpcResult.name === + 'ParticipantRequestCodeCanBeSent' && + (userChat = + window + .require('WAWebCollections') + .Chat.get(pWid) || + (await window + .require('WAWebCollections') + .Chat.find(pWid))) + ) { + const groupName = + group.formattedTitle || group.name; + const res = await window + .require('WAWebChatSendMessages') + .sendGroupInviteMessage( + userChat, + group.id._serialized, + groupName, + rpcResult.inviteV4Code, + rpcResult.inviteV4CodeExp, + comment, + await window.WWebJS.getProfilePicThumbToBase64( + groupWid, + ), + ); + isInviteV4Sent = res.messageSendResult === 'OK'; + } + + participantData[pId].isInviteV4Sent = isInviteV4Sent; } - participantData[pId].isInviteV4Sent = isInviteV4Sent; + sleep && + participantWids.length > 1 && + participantWids.indexOf(pWid) !== + participantWids.length - 1 && + (await new Promise((resolve) => + setTimeout(resolve, _getSleepTime(sleep)), + )); } - sleep && - participantWids.length > 1 && - participantWids.indexOf(pWid) !== participantWids.length - 1 && - (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep)))); - } - - return participantData; - }, this.id._serialized, participantIds, options); + return participantData; + }, + this.id._serialized, + participantIds, + options, + ); } /** * Removes a list of participants by ID to the group - * @param {Array<string>} participantIds + * @param {Array<string>} participantIds * @returns {Promise<{ status: number }>} */ async removeParticipants(participantIds) { - return await this.client.pupPage.evaluate(async (chatId, participantIds) => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - const participants = (await Promise.all(participantIds.map(async p => { - const { lid, phone } = await window.WWebJS.enforceLidAndPnRetrieval(p); - - return chat.groupMetadata.participants.get(lid?._serialized) || - chat.groupMetadata.participants.get(phone?._serialized); - }))).filter(Boolean); - await window.Store.GroupParticipants.removeParticipants(chat, participants); - return { status: 200 }; - }, this.id._serialized, participantIds); + return await this.client.pupPage.evaluate( + async (chatId, participantIds) => { + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + const participants = ( + await Promise.all( + participantIds.map(async (p) => { + const { lid, phone } = + await window.WWebJS.enforceLidAndPnRetrieval(p); + + return ( + chat.groupMetadata.participants.get( + lid?._serialized, + ) || + chat.groupMetadata.participants.get( + phone?._serialized, + ) + ); + }), + ) + ).filter(Boolean); + await window + .require('WAWebModifyParticipantsGroupAction') + .removeParticipants(chat, participants); + return { status: 200 }; + }, + this.id._serialized, + participantIds, + ); } /** * Promotes participants by IDs to admins - * @param {Array<string>} participantIds + * @param {Array<string>} participantIds * @returns {Promise<{ status: number }>} Object with status code indicating if the operation was successful */ async promoteParticipants(participantIds) { - return await this.client.pupPage.evaluate(async (chatId, participantIds) => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - const participants = (await Promise.all(participantIds.map(async p => { - const { lid, phone } = await window.WWebJS.enforceLidAndPnRetrieval(p); - - return chat.groupMetadata.participants.get(lid?._serialized) || - chat.groupMetadata.participants.get(phone?._serialized); - }))).filter(Boolean); - await window.Store.GroupParticipants.promoteParticipants(chat, participants); - return { status: 200 }; - }, this.id._serialized, participantIds); + return await this.client.pupPage.evaluate( + async (chatId, participantIds) => { + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + const participants = ( + await Promise.all( + participantIds.map(async (p) => { + const { lid, phone } = + await window.WWebJS.enforceLidAndPnRetrieval(p); + + return ( + chat.groupMetadata.participants.get( + lid?._serialized, + ) || + chat.groupMetadata.participants.get( + phone?._serialized, + ) + ); + }), + ) + ).filter(Boolean); + await window + .require('WAWebModifyParticipantsGroupAction') + .promoteParticipants(chat, participants); + return { status: 200 }; + }, + this.id._serialized, + participantIds, + ); } /** * Demotes participants by IDs to regular users - * @param {Array<string>} participantIds + * @param {Array<string>} participantIds * @returns {Promise<{ status: number }>} Object with status code indicating if the operation was successful */ async demoteParticipants(participantIds) { - return await this.client.pupPage.evaluate(async (chatId, participantIds) => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - const participants = (await Promise.all(participantIds.map(async p => { - const { lid, phone } = await window.WWebJS.enforceLidAndPnRetrieval(p); - - return chat.groupMetadata.participants.get(lid?._serialized) || - chat.groupMetadata.participants.get(phone?._serialized); - }))).filter(Boolean); - await window.Store.GroupParticipants.demoteParticipants(chat, participants); - return { status: 200 }; - }, this.id._serialized, participantIds); + return await this.client.pupPage.evaluate( + async (chatId, participantIds) => { + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + const participants = ( + await Promise.all( + participantIds.map(async (p) => { + const { lid, phone } = + await window.WWebJS.enforceLidAndPnRetrieval(p); + + return ( + chat.groupMetadata.participants.get( + lid?._serialized, + ) || + chat.groupMetadata.participants.get( + phone?._serialized, + ) + ); + }), + ) + ).filter(Boolean); + await window + .require('WAWebModifyParticipantsGroupAction') + .demoteParticipants(chat, participants); + return { status: 200 }; + }, + this.id._serialized, + participantIds, + ); } /** * Updates the group subject - * @param {string} subject + * @param {string} subject * @returns {Promise<boolean>} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions. */ async setSubject(subject) { - const success = await this.client.pupPage.evaluate(async (chatId, subject) => { - const chatWid = window.Store.WidFactory.createWid(chatId); - try { - await window.Store.GroupUtils.setGroupSubject(chatWid, subject); - return true; - } catch (err) { - if(err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, this.id._serialized, subject); + const success = await this.client.pupPage.evaluate( + async (chatId, subject) => { + const chatWid = window + .require('WAWebWidFactory') + .createWid(chatId); + try { + await window + .require('WAWebGroupModifyInfoJob') + .setGroupSubject(chatWid, subject); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + this.id._serialized, + subject, + ); - if(!success) return false; + if (!success) return false; this.name = subject; return true; } /** * Updates the group description - * @param {string} description + * @param {string} description * @returns {Promise<boolean>} Returns true if the description was properly updated. This can return false if the user does not have the necessary permissions. */ async setDescription(description) { - const success = await this.client.pupPage.evaluate(async (chatId, description) => { - const chatWid = window.Store.WidFactory.createWid(chatId); - let descId = window.Store.GroupMetadata.get(chatWid).descId; - let newId = await window.Store.MsgKey.newId(); - try { - await window.Store.GroupUtils.setGroupDescription(chatWid, description, newId, descId); - return true; - } catch (err) { - if(err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, this.id._serialized, description); + const success = await this.client.pupPage.evaluate( + async (chatId, description) => { + const chatWid = window + .require('WAWebWidFactory') + .createWid(chatId); + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + let descId = chat.groupMetadata.descId; + let newId = await window.require('WAWebMsgKey').newId(); + try { + await window + .require('WAWebGroupModifyInfoJob') + .setGroupDescription( + chatWid, + description, + newId, + descId, + ); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + this.id._serialized, + description, + ); - if(!success) return false; + if (!success) return false; this.groupMetadata.desc = description; 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 + * @param {boolean} [adminsOnly=true] Enable or disable this option * @returns {Promise<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 chat = await window.WWebJS.getChat(groupId, { getAsModel: false }); - try { - await window.Store.GroupUtils.setGroupProperty(chat, 'member_add_mode', adminsOnly ? 0 : 1); - return true; - } catch (err) { - if(err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, this.id._serialized, adminsOnly); - - success && (this.groupMetadata.memberAddMode = adminsOnly ? 'admin_add' : 'all_member_add'); + async setAddMembersAdminsOnly(adminsOnly = true) { + const success = await this.client.pupPage.evaluate( + async (groupId, adminsOnly) => { + const chat = await window.WWebJS.getChat(groupId, { + getAsModel: false, + }); + try { + await window + .require('WAWebSetPropertyGroupAction') + .setGroupProperty( + chat, + 'member_add_mode', + adminsOnly ? 0 : 1, + ); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + this.id._serialized, + adminsOnly, + ); + + success && + (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 + * @param {boolean} [adminsOnly=true] Enable or disable this option * @returns {Promise<boolean>} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions. */ - async setMessagesAdminsOnly(adminsOnly=true) { - const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - try { - await window.Store.GroupUtils.setGroupProperty(chat, 'announcement', adminsOnly ? 1 : 0); - return true; - } catch (err) { - if(err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, this.id._serialized, adminsOnly); + async setMessagesAdminsOnly(adminsOnly = true) { + const success = await this.client.pupPage.evaluate( + async (chatId, adminsOnly) => { + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + try { + await window + .require('WAWebSetPropertyGroupAction') + .setGroupProperty( + chat, + 'announcement', + adminsOnly ? 1 : 0, + ); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + this.id._serialized, + adminsOnly, + ); - if(!success) return false; + if (!success) return false; this.groupMetadata.announce = adminsOnly; return true; @@ -367,27 +543,35 @@

Source: structures/GroupChat.js

/** * Updates the group settings to only allow admins to edit group info (title, description, photo). - * @param {boolean} [adminsOnly=true] Enable or disable this option + * @param {boolean} [adminsOnly=true] Enable or disable this option * @returns {Promise<boolean>} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions. */ - async setInfoAdminsOnly(adminsOnly=true) { - const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - try { - await window.Store.GroupUtils.setGroupProperty(chat, 'restrict', adminsOnly ? 1 : 0); - return true; - } catch (err) { - if(err.name === 'ServerStatusCodeError') return false; - throw err; - } - }, this.id._serialized, adminsOnly); + async setInfoAdminsOnly(adminsOnly = true) { + const success = await this.client.pupPage.evaluate( + async (chatId, adminsOnly) => { + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + try { + await window + .require('WAWebSetPropertyGroupAction') + .setGroupProperty(chat, 'restrict', adminsOnly ? 1 : 0); + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }, + this.id._serialized, + adminsOnly, + ); + + if (!success) return false; - if(!success) return false; - this.groupMetadata.restrict = adminsOnly; return true; } - + /** * Deletes the group's picture. * @returns {Promise<boolean>} Returns true if the picture was properly deleted. This can return false if the user does not have the necessary permissions. @@ -406,9 +590,13 @@

Source: structures/GroupChat.js

* @returns {Promise<boolean>} Returns true if the picture was properly updated. This can return false if the user does not have the necessary permissions. */ async setPicture(media) { - const success = await this.client.pupPage.evaluate((chatid, media) => { - return window.WWebJS.setPicture(chatid, media); - }, this.id._serialized, media); + const success = await this.client.pupPage.evaluate( + (chatid, media) => { + return window.WWebJS.setPicture(chatid, media); + }, + this.id._serialized, + media, + ); return success; } @@ -418,37 +606,35 @@

Source: structures/GroupChat.js

* @returns {Promise<string>} Group's invite code */ async getInviteCode() { - const codeRes = await this.client.pupPage.evaluate(async chatId => { - const chatWid = window.Store.WidFactory.createWid(chatId); + const codeRes = await this.client.pupPage.evaluate(async (chatId) => { try { - return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1020730154') - ? await window.Store.GroupInvite.fetchMexGroupInviteCode(chatId) - : await window.Store.GroupInvite.queryGroupInviteCode(chatWid, true); - } - catch (err) { - if(err.name === 'ServerStatusCodeError') return undefined; + return await window + .require('WAWebMexFetchGroupInviteCodeJob') + .fetchMexGroupInviteCode(chatId); + } catch (err) { + if (err.name === 'ServerStatusCodeError') return undefined; throw err; } }, this.id._serialized); - return codeRes?.code - ? codeRes?.code - : codeRes; + return codeRes?.code ? codeRes?.code : codeRes; } - + /** * Invalidates the current group invite code and generates a new one * @returns {Promise<string>} New invite code */ async revokeInvite() { - const codeRes = await this.client.pupPage.evaluate(chatId => { - const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.GroupInvite.resetGroupInviteCode(chatWid); + const codeRes = await this.client.pupPage.evaluate((chatId) => { + const chatWid = window.require('WAWebWidFactory').createWid(chatId); + return window + .require('WAWebGroupQueryJob') + .resetGroupInviteCode(chatWid); }, this.id._serialized); return codeRes.code; } - + /** * An object that handles the information about the group membership request * @typedef {Object} GroupMembershipRequest @@ -458,13 +644,15 @@

Source: structures/GroupChat.js

* @property {string} requestMethod The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin * @property {number} t The timestamp the request was created at */ - + /** * Gets an array of membership requests * @returns {Promise<Array<GroupMembershipRequest>>} An array of membership requests */ async getGroupMembershipRequests() { - return await this.client.getGroupMembershipRequests(this.id._serialized); + return await this.client.getGroupMembershipRequests( + this.id._serialized, + ); } /** @@ -488,7 +676,10 @@

Source: structures/GroupChat.js

* @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned */ async approveGroupMembershipRequests(options = {}) { - return await this.client.approveGroupMembershipRequests(this.id._serialized, options); + return await this.client.approveGroupMembershipRequests( + this.id._serialized, + options, + ); } /** @@ -497,7 +688,10 @@

Source: structures/GroupChat.js

* @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned */ async rejectGroupMembershipRequests(options = {}) { - return await this.client.rejectGroupMembershipRequests(this.id._serialized, options); + return await this.client.rejectGroupMembershipRequests( + this.id._serialized, + options, + ); } /** @@ -505,12 +699,13 @@

Source: structures/GroupChat.js

* @returns {Promise} */ async leave() { - await this.client.pupPage.evaluate(async chatId => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - return window.Store.GroupUtils.sendExitGroup(chat); + await this.client.pupPage.evaluate(async (chatId) => { + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + return window.require('WAWebExitGroupAction').sendExitGroup(chat); }, this.id._serialized); } - } module.exports = GroupChat; @@ -524,7 +719,7 @@

Source: structures/GroupChat.js

diff --git a/docs/structures_GroupNotification.js.html b/docs/structures_GroupNotification.js.html index 085bdda288a..8416a61479b 100644 --- a/docs/structures_GroupNotification.js.html +++ b/docs/structures_GroupNotification.js.html @@ -41,7 +41,7 @@

Source: structures/GroupNotification.js

constructor(client, data) { super(client); - if(data) this._patch(data); + if (data) this._patch(data); } _patch(data) { @@ -57,12 +57,12 @@

Source: structures/GroupNotification.js

*/ this.body = data.body || ''; - /** + /** * GroupNotification type * @type {GroupNotificationTypes} */ this.type = data.subtype; - + /** * Unix timestamp for when the groupNotification was created * @type {number} @@ -71,17 +71,23 @@

Source: structures/GroupNotification.js

/** * ID for the Chat that this groupNotification was sent for. - * + * * @type {string} */ - this.chatId = typeof (data.id.remote) === 'object' ? data.id.remote._serialized : data.id.remote; + this.chatId = + typeof data.id.remote === 'object' + ? data.id.remote._serialized + : data.id.remote; /** * ContactId for the user that produced the GroupNotification. * @type {string} */ - this.author = typeof (data.author) === 'object' ? data.author._serialized : data.author; - + this.author = + typeof data.author === 'object' + ? data.author._serialized + : data.author; + /** * Contact IDs for the users that were affected by this GroupNotification. * @type {Array<string>} @@ -116,20 +122,23 @@

Source: structures/GroupNotification.js

* @returns {Promise<Array<Contact>>} */ async getRecipients() { - return await Promise.all(this.recipientIds.map(async m => await this.client.getContactById(m))); + return await Promise.all( + this.recipientIds.map( + async (m) => await this.client.getContactById(m), + ), + ); } /** * Sends a message to the same chat this GroupNotification was produced in. - * - * @param {string|MessageMedia|Location} content + * + * @param {string|MessageMedia|Location} content * @param {object} options * @returns {Promise<Message>} */ - async reply(content, options={}) { + async reply(content, options = {}) { return this.client.sendMessage(this.chatId, content, options); } - } module.exports = GroupNotification; @@ -143,7 +152,7 @@

Source: structures/GroupNotification.js

diff --git a/docs/structures_Label.js.html b/docs/structures_Label.js.html index 3426c2f631a..728613242b0 100644 --- a/docs/structures_Label.js.html +++ b/docs/structures_Label.js.html @@ -43,13 +43,13 @@

Source: structures/Label.js

* @param {Base} client * @param {object} labelData */ - constructor(client, labelData){ + constructor(client, labelData) { super(client); - if(labelData) this._patch(labelData); + if (labelData) this._patch(labelData); } - _patch(labelData){ + _patch(labelData) { /** * Label ID * @type {string} @@ -72,13 +72,13 @@

Source: structures/Label.js

* Get all chats that have been assigned this Label * @returns {Promise<Array<Chat>>} */ - async getChats(){ + async getChats() { return this.client.getChatsByLabelId(this.id); } - } -module.exports = Label; +module.exports = Label; + @@ -88,7 +88,7 @@

Source: structures/Label.js

diff --git a/docs/structures_List.js.html b/docs/structures_List.js.html index 3c4d64fe1ec..170c2b21119 100644 --- a/docs/structures_List.js.html +++ b/docs/structures_List.js.html @@ -56,13 +56,12 @@

Source: structures/List.js

* @type {string} */ this.buttonText = buttonText; - + /** * title of message * @type {string} */ this.title = title; - /** * footer of message @@ -75,9 +74,8 @@

Source: structures/List.js

* @type {Array<any>} */ this.sections = this._format(sections); - } - + /** * Creates section array from simple array * @param {Array<any>} sections @@ -86,25 +84,35 @@

Source: structures/List.js

* Input: [{title:'sectionTitle',rows:[{id:'customId', title:'ListItem2', description: 'desc'},{title:'ListItem2'}]}}] * Returns: [{'title':'sectionTitle','rows':[{'rowId':'customId','title':'ListItem1','description':'desc'},{'rowId':'oGSRoD','title':'ListItem2','description':''}]}] */ - _format(sections){ - if(!sections.length){throw '[LT02] List without sections';} - if(sections.length > 1 && sections.filter(s => typeof s.title == 'undefined').length > 1){throw '[LT05] You can\'t have more than one empty title.';} - return sections.map( (section) =>{ - if(!section.rows.length){throw '[LT03] Section without rows';} + _format(sections) { + if (!sections.length) { + throw '[LT02] List without sections'; + } + if ( + sections.length > 1 && + sections.filter((s) => typeof s.title == 'undefined').length > 1 + ) { + throw "[LT05] You can't have more than one empty title."; + } + return sections.map((section) => { + if (!section.rows.length) { + throw '[LT03] Section without rows'; + } return { title: section.title ? section.title : undefined, - rows: section.rows.map( (row) => { - if(!row.title){throw '[LT04] Row without title';} + rows: section.rows.map((row) => { + if (!row.title) { + throw '[LT04] Row without title'; + } return { rowId: row.id ? row.id : Util.generateHash(6), title: row.title, - description: row.description ? row.description : '' + description: row.description ? row.description : '', }; - }) + }), }; }); } - } module.exports = List; @@ -118,7 +126,7 @@

Source: structures/List.js

diff --git a/docs/structures_Location.js.html b/docs/structures_Location.js.html index 83df2184064..187106b28a2 100644 --- a/docs/structures_Location.js.html +++ b/docs/structures_Location.js.html @@ -84,13 +84,15 @@

Source: structures/Location.js

* Location full description * @type {string|undefined} */ - this.description = this.name && this.address - ? `${this.name}\n${this.address}` - : this.name || this.address || ''; + this.description = + this.name && this.address + ? `${this.name}\n${this.address}` + : this.name || this.address || ''; } } -module.exports = Location; +module.exports = Location; + @@ -100,7 +102,7 @@

Source: structures/Location.js

diff --git a/docs/structures_Message.js.html b/docs/structures_Message.js.html index fe82a909ee8..8b5894b32d2 100644 --- a/docs/structures_Message.js.html +++ b/docs/structures_Message.js.html @@ -54,13 +54,13 @@

Source: structures/Message.js

_patch(data) { this._data = data; - + /** * MediaKey that represents the sticker 'ID' * @type {string} */ this.mediaKey = data.mediaKey; - + /** * ID that represents the message * @type {object} @@ -83,7 +83,9 @@

Source: structures/Message.js

* Message content * @type {string} */ - this.body = this.hasMedia ? data.caption || '' : data.body || data.pollName || data.eventName || ''; + this.body = this.hasMedia + ? data.caption || '' + : data.body || data.pollName || data.eventName || ''; /** * Message type @@ -101,7 +103,10 @@

Source: structures/Message.js

* ID for the Chat that this message was sent to, except if the message was sent by the current user. * @type {string} */ - this.from = (typeof (data.from) === 'object' && data.from !== null) ? data.from._serialized : data.from; + this.from = + typeof data.from === 'object' && data.from !== null + ? data.from._serialized + : data.from; /** * ID for who this message is for. @@ -110,19 +115,31 @@

Source: structures/Message.js

* If the message is sent by another user, it will be the ID for the current user. * @type {string} */ - this.to = (typeof (data.to) === 'object' && data.to !== null) ? data.to._serialized : data.to; + this.to = + typeof data.to === 'object' && data.to !== null + ? data.to._serialized + : data.to; /** * If the message was sent to a group, this field will contain the user that sent the message. * @type {string} */ - this.author = (typeof (data.author) === 'object' && data.author !== null) ? data.author._serialized : data.author; + this.author = + typeof data.author === 'object' && data.author !== null + ? data.author._serialized + : data.author; /** * String that represents from which device type the message was sent * @type {string} */ - this.deviceType = typeof data.id.id === 'string' && data.id.id.length > 21 ? 'android' : typeof data.id.id === 'string' && data.id.id.substring(0, 2) === '3A' ? 'ios' : 'web'; + this.deviceType = + typeof data.id.id === 'string' && data.id.id.length > 21 + ? 'android' + : typeof data.id.id === 'string' && + data.id.id.substring(0, 2) === '3A' + ? 'ios' + : 'web'; /** * Indicates if the message was forwarded * @type {boolean} @@ -141,7 +158,8 @@

Source: structures/Message.js

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

Source: structures/Message.js

description = { name: splitted[0], address: splitted[1], - url: data.clientUrl + url: data.clientUrl, }; } return new Location(data.lat, data.lng, description); @@ -203,20 +221,36 @@

Source: structures/Message.js

* List of vCards contained in the message. * @type {Array<string>} */ - this.vCards = data.type === MessageTypes.CONTACT_CARD_MULTI ? data.vcardList.map((c) => c.vcard) : data.type === MessageTypes.CONTACT_CARD ? [data.body] : []; + this.vCards = + data.type === MessageTypes.CONTACT_CARD_MULTI + ? data.vcardList.map((c) => c.vcard) + : data.type === MessageTypes.CONTACT_CARD + ? [data.body] + : []; /** * Group Invite Data * @type {object} */ - this.inviteV4 = data.type === MessageTypes.GROUP_INVITE ? { - inviteCode: data.inviteCode, - inviteCodeExp: data.inviteCodeExp, - groupId: data.inviteGrp, - groupName: data.inviteGrpName, - fromId: typeof data.from === 'object' && '_serialized' in data.from ? data.from._serialized : data.from, - toId: typeof data.to === 'object' && '_serialized' in data.to ? data.to._serialized : data.to - } : undefined; + this.inviteV4 = + data.type === MessageTypes.GROUP_INVITE + ? { + inviteCode: data.inviteCode, + inviteCodeExp: data.inviteCodeExp, + groupId: data.inviteGrp, + groupName: data.inviteGrpName, + fromId: + typeof data.from === 'object' && + '_serialized' in data.from + ? data.from._serialized + : data.from, + toId: + typeof data.to === 'object' && + '_serialized' in data.to + ? data.to._serialized + : data.to, + } + : undefined; /** * Indicates the mentions in the message body. @@ -247,7 +281,7 @@

Source: structures/Message.js

*/ this.token = data.token ? data.token : undefined; - /** + /** * Indicates whether the message is a Gif * @type {boolean} */ @@ -288,7 +322,7 @@

Source: structures/Message.js

if (data.latestEditMsgKey) { this.latestEditMsgKey = data.latestEditMsgKey; } - + /** * Protocol message key. * Can be used to retrieve the ID of an original message that was revoked. @@ -315,17 +349,27 @@

Source: structures/Message.js

} /** Selected List row Id **/ - if (data.listResponse && data.listResponse.singleSelectReply.selectedRowId) { - this.selectedRowId = data.listResponse.singleSelectReply.selectedRowId; + if ( + data.listResponse && + data.listResponse.singleSelectReply.selectedRowId + ) { + this.selectedRowId = + data.listResponse.singleSelectReply.selectedRowId; } if (this.type === MessageTypes.POLL_CREATION) { this.pollName = data.pollName; this.pollOptions = data.pollOptions; - this.allowMultipleAnswers = Boolean(!data.pollSelectableOptionsCount); + this.allowMultipleAnswers = Boolean( + !data.pollSelectableOptionsCount, + ); this.pollInvalidated = data.pollInvalidated; this.isSentCagPollCreation = data.isSentCagPollCreation; - this.messageSecret = data.messageSecret ? Object.keys(data.messageSecret).map((key) => data.messageSecret[key]) : []; + this.messageSecret = data.messageSecret + ? Object.keys(data.messageSecret).map( + (key) => data.messageSecret[key], + ) + : []; } return super._patch(data); @@ -336,19 +380,25 @@

Source: structures/Message.js

} /** - * Reloads this Message object's data in-place with the latest values from WhatsApp Web. + * Reloads this Message object's data in-place with the latest values from WhatsApp Web. * Note that the Message must still be in the web app cache for this to work, otherwise will return null. * @returns {Promise<Message>} */ async reload() { const newData = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; if (!msg) return null; return window.WWebJS.getMessageModel(msg); }, this.id._serialized); - if(!newData) return null; - + if (!newData) return null; + this._patch(newData); return this; } @@ -360,7 +410,7 @@

Source: structures/Message.js

get rawData() { return this._data; } - + /** * Returns the Chat this message was sent in * @returns {Promise<Chat>} @@ -382,15 +432,27 @@

Source: structures/Message.js

* @returns {Promise<Array<Contact>>} */ async getMentions() { - return await Promise.all(this.mentionedIds.map(async m => await this.client.getContactById(m))); + return await Promise.all( + this.mentionedIds.map( + async (m) => + await this.client.getContactById( + typeof m === 'string' ? m : m._serialized, + ), + ), + ); } - + /** * Returns groups mentioned in this message * @returns {Promise<Array<GroupChat>>} */ async getGroupMentions() { - return await Promise.all(this.groupMentions.map(async (m) => await this.client.getChatById(m.groupJid._serialized))); + return await Promise.all( + this.groupMentions.map( + async (m) => + await this.client.getChatById(m.groupJid._serialized), + ), + ); } /** @@ -401,8 +463,16 @@

Source: structures/Message.js

if (!this.hasQuotedMsg) return undefined; 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); + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + const quotedMsg = window + .require('WAWebQuotedMsgModelUtils') + .getQuotedMsgObj(msg); return window.WWebJS.getMessageModel(quotedMsg); }, this.id._serialized); @@ -426,7 +496,7 @@

Source: structures/Message.js

options = { ...options, - quotedMessageId: this.id._serialized + quotedMessageId: this.id._serialized, }; return this.client.sendMessage(chatId, content, options); @@ -437,14 +507,8 @@

Source: structures/Message.js

* @param {string} reaction - Emoji to react with. Send an empty string to remove the reaction. * @return {Promise} */ - async react(reaction){ - await this.client.pupPage.evaluate(async (messageId, reaction) => { - 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); + async react(reaction) { + return this.client.sendReaction(this.id._serialized, reaction); } /** @@ -464,9 +528,13 @@

Source: structures/Message.js

async forward(chat) { const chatId = typeof chat === 'string' ? chat : chat.id._serialized; - await this.client.pupPage.evaluate(async (msgId, chatId) => { - return window.WWebJS.forwardMessage(chatId, msgId); - }, this.id._serialized, chatId); + await this.client.pupPage.evaluate( + async (msgId, chatId) => { + return window.WWebJS.forwardMessage(chatId, msgId); + }, + this.id._serialized, + chatId, + ); } /** @@ -479,57 +547,84 @@

Source: structures/Message.js

} const result = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; // REUPLOADING mediaStage means the media is expired and the download button is spinning, cannot be downloaded now - if (!msg || !msg.mediaData || msg.mediaData.mediaStage === 'REUPLOADING') { + if ( + !msg || + !msg.mediaData || + msg.mediaData.mediaStage === 'REUPLOADING' + ) { return null; } if (msg.mediaData.mediaStage != 'RESOLVED') { // try to resolve media await msg.downloadMedia({ downloadEvenIfExpensive: true, - rmrReason: 1 + rmrReason: 1, }); } - if (msg.mediaData.mediaStage.includes('ERROR') || msg.mediaData.mediaStage === 'FETCHING') { + if ( + msg.mediaData.mediaStage.includes('ERROR') || + msg.mediaData.mediaStage === 'FETCHING' + ) { // media could not be downloaded return undefined; } try { const mockQpl = { - addAnnotations: function() { return this; }, - addPoint: function() { return this; } + addAnnotations: function () { + return this; + }, + addPoint: function () { + return this; + }, }; - const decryptedMedia = await window.Store.DownloadManager.downloadAndMaybeDecrypt({ - directPath: msg.directPath, - encFilehash: msg.encFilehash, - filehash: msg.filehash, - mediaKey: msg.mediaKey, - mediaKeyTimestamp: msg.mediaKeyTimestamp, - type: msg.type, - signal: (new AbortController).signal, - downloadQpl: mockQpl - }); - - const data = await window.WWebJS.arrayBufferToBase64Async(decryptedMedia); + const decryptedMedia = await window + .require('WAWebDownloadManager') + .downloadManager.downloadAndMaybeDecrypt({ + directPath: msg.directPath, + encFilehash: msg.encFilehash, + filehash: msg.filehash, + mediaKey: msg.mediaKey, + mediaKeyTimestamp: msg.mediaKeyTimestamp, + type: msg.type, + signal: new AbortController().signal, + downloadQpl: mockQpl, + }); + + const data = + await window.WWebJS.arrayBufferToBase64Async( + decryptedMedia, + ); return { data, mimetype: msg.mimetype, filename: msg.filename, - filesize: msg.size + filesize: msg.size, }; } catch (e) { - if(e.status && e.status === 404) return undefined; + if (e.status && e.status === 404) return undefined; throw e; } }, this.id._serialized); if (!result) return undefined; - return new MessageMedia(result.mimetype, result.data, result.filename, result.filesize); + return new MessageMedia( + result.mimetype, + result.data, + result.filename, + result.filesize, + ); } /** @@ -538,23 +633,66 @@

Source: structures/Message.js

* @param {?boolean} [clearMedia = true] If true, any associated media will also be deleted from a device. */ async delete(everyone, clearMedia = true) { - await this.client.pupPage.evaluate(async (msgId, everyone, clearMedia) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; - const chat = window.Store.Chat.get(msg.id.remote) || (await window.Store.Chat.find(msg.id.remote)); - - const canRevoke = - window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg); - - if (everyone && canRevoke) { - return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') - ? window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: clearMedia }) - : window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); - } + await this.client.pupPage.evaluate( + async (msgId, everyone, clearMedia) => { + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + const chat = + window + .require('WAWebCollections') + .Chat.get(msg.id.remote) || + (await window + .require('WAWebCollections') + .Chat.find(msg.id.remote)); + + const canRevoke = + window + .require('WAWebMsgActionCapability') + .canSenderRevokeMsg(msg) || + window + .require('WAWebMsgActionCapability') + .canAdminRevokeMsg(msg); + + const { Cmd } = window.require('WAWebCmd'); + + if (everyone && canRevoke) { + return window.WWebJS.compareWwebVersions( + window.Debug.VERSION, + '>=', + '2.3000.0', + ) + ? Cmd.sendRevokeMsgs( + chat, + { list: [msg], type: 'message' }, + { clearMedia: clearMedia }, + ) + : Cmd.sendRevokeMsgs(chat, [msg], { + clearMedia: true, + type: msg.id.fromMe ? 'Sender' : 'Admin', + }); + } - return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') - ? window.Store.Cmd.sendDeleteMsgs(chat, { list: [msg], type: 'message' }, clearMedia) - : window.Store.Cmd.sendDeleteMsgs(chat, [msg], clearMedia); - }, this.id._serialized, everyone, clearMedia); + return window.WWebJS.compareWwebVersions( + window.Debug.VERSION, + '>=', + '2.3000.0', + ) + ? Cmd.sendDeleteMsgs( + chat, + { list: [msg], type: 'message' }, + clearMedia, + ) + : Cmd.sendDeleteMsgs(chat, [msg], clearMedia); + }, + this.id._serialized, + everyone, + clearMedia, + ); } /** @@ -562,10 +700,20 @@

Source: structures/Message.js

*/ async star() { await this.client.pupPage.evaluate(async (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); + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + if (window.require('WAWebMsgActionCapability').canStarMsg(msg)) { + let chat = await window + .require('WAWebCollections') + .Chat.find(msg.id.remote); + return window + .require('WAWebCmd') + .Cmd.sendStarMsgs(chat, [msg], false); } }, this.id._serialized); } @@ -575,10 +723,20 @@

Source: structures/Message.js

*/ async unstar() { await this.client.pupPage.evaluate(async (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); + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + if (window.require('WAWebMsgActionCapability').canStarMsg(msg)) { + let chat = await window + .require('WAWebCollections') + .Chat.find(msg.id.remote); + return window + .require('WAWebCmd') + .Cmd.sendUnstarMsgs(chat, [msg], false); } }, this.id._serialized); } @@ -589,9 +747,17 @@

Source: structures/Message.js

* @returns {Promise<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); + return await this.client.pupPage.evaluate( + async (msgId, duration) => { + return await window.WWebJS.pinUnpinMsgAction( + msgId, + 1, + duration, + ); + }, + this.id._serialized, + duration, + ); } /** @@ -622,13 +788,28 @@

Source: structures/Message.js

*/ async getInfo() { const info = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; if (!msg || !msg.id.fromMe) return null; return new Promise((resolve) => { - setTimeout(async () => { - resolve(await window.Store.getMsgInfo(msg.id)); - }, (Date.now() - msg.t * 1000 < 1250) && Math.floor(Math.random() * (1200 - 1100 + 1)) + 1100 || 0); + setTimeout( + async () => { + resolve( + await window + .require('WAWebApiMessageInfoStore') + .queryMsgInfo(msg.id), + ); + }, + (Date.now() - msg.t * 1000 < 1250 && + Math.floor(Math.random() * (1200 - 1100 + 1)) + 1100) || + 0, + ); }); }, this.id._serialized); @@ -641,9 +822,14 @@

Source: structures/Message.js

*/ async getOrder() { if (this.type === MessageTypes.ORDER) { - const result = await this.client.pupPage.evaluate((orderId, token, chatId) => { - return window.WWebJS.getOrderDetail(orderId, token, chatId); - }, this.orderId, this.token, this._getChatId()); + const result = await this.client.pupPage.evaluate( + (orderId, token, chatId) => { + return window.WWebJS.getOrderDetail(orderId, token, chatId); + }, + this.orderId, + this.token, + this._getChatId(), + ); if (!result) return undefined; return new Order(this.client, result); } @@ -656,8 +842,14 @@

Source: structures/Message.js

async getPayment() { if (this.type === MessageTypes.PAYMENT) { const msg = await this.client.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; - if(!msg) return null; + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + if (!msg) return null; return msg.serialize(); }, this.id._serialized); return new Payment(this.client, msg); @@ -665,7 +857,6 @@

Source: structures/Message.js

return undefined; } - /** * Reaction List * @typedef {Object} ReactionList @@ -685,7 +876,9 @@

Source: structures/Message.js

} const reactions = await this.client.pupPage.evaluate(async (msgId) => { - const msgReactions = await window.Store.Reactions.find(msgId); + const msgReactions = await window + .require('WAWebCollections') + .Reactions.find(msgId); if (!msgReactions || !msgReactions.reactions.length) return null; return msgReactions.reactions.serialize(); }, this.id._serialized); @@ -694,8 +887,8 @@

Source: structures/Message.js

return undefined; } - return reactions.map(reaction => { - reaction.senders = reaction.senders.map(sender => { + return reactions.map((reaction) => { + reaction.senders = reaction.senders.map((sender) => { sender.timestamp = Math.round(sender.timestamp / 1000); return new Reaction(this.client, sender); }); @@ -711,36 +904,68 @@

Source: structures/Message.js

*/ async edit(content, options = {}) { if (options.mentions) { - !Array.isArray(options.mentions) && (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); + !Array.isArray(options.mentions) && + (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 && !Array.isArray(options.groupMentions) && (options.groupMentions = [options.groupMentions]); + options.groupMentions && + !Array.isArray(options.groupMentions) && + (options.groupMentions = [options.groupMentions]); let internalOptions = { linkPreview: options.linkPreview === false ? undefined : true, mentionedJidList: options.mentions || [], groupMentions: options.groupMentions, - extraOptions: options.extra + extraOptions: options.extra, }; - + if (!this.fromMe) { return null; } - const messageEdit = await this.client.pupPage.evaluate(async (msgId, message, options) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; - if (!msg) return null; - - 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(); - } - return null; - }, this.id._serialized, content, internalOptions); + const messageEdit = await this.client.pupPage.evaluate( + async (msgId, message, options) => { + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + if (!msg) return null; + + let canEdit = + window + .require('WAWebMsgActionCapability') + .canEditText(msg) || + window + .require('WAWebMsgActionCapability') + .canEditCaption(msg); + if (canEdit) { + const msgEdit = await window.WWebJS.editMessage( + msg, + message, + options, + ); + return msgEdit.serialize(); + } + return null; + }, + this.id._serialized, + content, + internalOptions, + ); if (messageEdit) { return new Message(this.client, messageEdit); } @@ -758,25 +983,40 @@

Source: structures/Message.js

return null; } - const edittedEventMsg = await this.client.pupPage.evaluate(async (msgId, editedEventObject) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; - if (!msg) return null; + const edittedEventMsg = await this.client.pupPage.evaluate( + async (msgId, editedEventObject) => { + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + if (!msg) return null; + + const { name, startTimeTs, eventSendOptions } = + editedEventObject; + const eventOptions = { + name: name, + description: eventSendOptions.description, + startTime: startTimeTs, + endTime: eventSendOptions.endTimeTs, + location: eventSendOptions.location, + callType: eventSendOptions.callType, + isEventCanceled: eventSendOptions.isEventCanceled, + }; - const { name, startTimeTs, eventSendOptions } = editedEventObject; - const eventOptions = { - name: name, - description: eventSendOptions.description, - startTime: startTimeTs, - endTime: eventSendOptions.endTimeTs, - location: eventSendOptions.location, - callType: eventSendOptions.callType, - isEventCanceled: eventSendOptions.isEventCanceled, - }; - - await window.Store.ScheduledEventMsgUtils.sendEventEditMessage(eventOptions, msg); - const editedMsg = window.Store.Msg.get(msg.id._serialized); - return editedMsg?.serialize(); - }, this.id._serialized, editedEventObject); + await window + .require('WAWebSendEventEditMsgAction') + .sendEventEditMessage(eventOptions, msg); + const editedMsg = window + .require('WAWebCollections') + .Msg.get(msg.id._serialized); + return editedMsg?.serialize(); + }, + this.id._serialized, + editedEventObject, + ); return edittedEventMsg && new Message(this.client, edittedEventMsg); } @@ -794,24 +1034,36 @@

Source: structures/Message.js

* @returns {Promise} */ async vote(selectedOptions) { - if (this.type != MessageTypes.POLL_CREATION) throw 'Invalid usage! Can only be used with a pollCreation message'; - - await this.client.pupPage.evaluate(async (messageId, votes) => { - if (!messageId) return null; - if (!Array.isArray(votes)) votes = [votes]; - let localIdSet = new Set(); - const msg = - window.Store.Msg.get(messageId) || (await window.Store.Msg.getMessagesById([messageId]))?.messages?.[0]; - if (!msg) return null; - - msg.pollOptions.forEach(a => { - for (const option of votes) { - if (a.name === option) localIdSet.add(a.localId); - } - }); + if (this.type != MessageTypes.POLL_CREATION) + throw 'Invalid usage! Can only be used with a pollCreation message'; + + await this.client.pupPage.evaluate( + async (messageId, votes) => { + if (!messageId) return null; + if (!Array.isArray(votes)) votes = [votes]; + let localIdSet = new Set(); + const msg = + window.require('WAWebCollections').Msg.get(messageId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([messageId]) + )?.messages?.[0]; + if (!msg) return null; + + msg.pollOptions.forEach((a) => { + for (const option of votes) { + if (a.name === option) localIdSet.add(a.localId); + } + }); - await window.Store.PollsSendVote.sendVote(msg, localIdSet); - }, this.id._serialized, selectedOptions); + await window + .require('WAWebPollsSendVoteMsgAction') + .sendVote(msg, localIdSet); + }, + this.id._serialized, + selectedOptions, + ); } } @@ -826,7 +1078,7 @@

Source: structures/Message.js

diff --git a/docs/structures_MessageMedia.js.html b/docs/structures_MessageMedia.js.html index 1bded98ee1f..fb7f5cf21a8 100644 --- a/docs/structures_MessageMedia.js.html +++ b/docs/structures_MessageMedia.js.html @@ -63,7 +63,7 @@

Source: structures/MessageMedia.js

* @type {?string} */ this.filename = filename; - + /** * Document file size in bytes. Value can be null * @type {?number} @@ -73,12 +73,12 @@

Source: structures/MessageMedia.js

/** * Creates a MessageMedia instance from a local file path - * @param {string} filePath + * @param {string} filePath * @returns {MessageMedia} */ static fromFilePath(filePath) { - const b64data = fs.readFileSync(filePath, {encoding: 'base64'}); - const mimetype = mime.getType(filePath); + const b64data = fs.readFileSync(filePath, { encoding: 'base64' }); + const mimetype = mime.getType(filePath); const filename = path.basename(filePath); return new MessageMedia(mimetype, b64data, filename); @@ -100,16 +100,25 @@

Source: structures/MessageMedia.js

let mimetype = mime.getType(pUrl.pathname); if (!mimetype && !options.unsafeMime) - throw new Error('Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.'); - - async function fetchData (url, options) { - const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options); + throw new Error( + 'Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.', + ); + + async function fetchData(url, options) { + const reqOptions = Object.assign( + { headers: { accept: 'image/* video/* text/* audio/*' } }, + options, + ); const response = await fetch(url, reqOptions); const mime = response.headers.get('Content-Type'); const size = response.headers.get('Content-Length'); - const contentDisposition = response.headers.get('Content-Disposition'); - const name = contentDisposition ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) : null; + const contentDisposition = response.headers.get( + 'Content-Disposition', + ); + const name = contentDisposition + ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) + : null; let data = ''; if (response.buffer) { @@ -121,19 +130,23 @@

Source: structures/MessageMedia.js

}); data = btoa(data); } - + return { data, mime, name, size }; } const res = options.client - ? (await options.client.pupPage.evaluate(fetchData, url, options.reqOptions)) - : (await fetchData(url, options.reqOptions)); + ? await options.client.pupPage.evaluate( + fetchData, + url, + options.reqOptions, + ) + : await fetchData(url, options.reqOptions); + + const filename = + options.filename || + (res.name ? res.name[0] : pUrl.pathname.split('/').pop() || 'file'); - const filename = options.filename || - (res.name ? res.name[0] : (pUrl.pathname.split('/').pop() || 'file')); - - if (!mimetype) - mimetype = res.mime; + if (!mimetype) mimetype = res.mime; return new MessageMedia(mimetype, res.data, filename, res.size || null); } @@ -150,7 +163,7 @@

Source: structures/MessageMedia.js

diff --git a/docs/structures_Order.js.html b/docs/structures_Order.js.html index ab2ba91fac2..689afab9c42 100644 --- a/docs/structures_Order.js.html +++ b/docs/structures_Order.js.html @@ -51,7 +51,9 @@

Source: structures/Order.js

* @type {Array<Product>} */ if (data.products) { - this.products = data.products.map(product => new Product(this.client, product)); + this.products = data.products.map( + (product) => new Product(this.client, product), + ); } /** * Order Subtotal @@ -76,11 +78,10 @@

Source: structures/Order.js

return super._patch(data); } - - } -module.exports = Order; +module.exports = Order; + @@ -90,7 +91,7 @@

Source: structures/Order.js

diff --git a/docs/structures_Payment.js.html b/docs/structures_Payment.js.html index ddb93c848ed..213b3cce24f 100644 --- a/docs/structures_Payment.js.html +++ b/docs/structures_Payment.js.html @@ -85,7 +85,7 @@

Source: structures/Payment.js

* 9:CANCELLED * 10:WAITING_FOR_PAYER * 11:WAITING - * + * * @type {number} */ this.paymentStatus = data.paymentStatus; @@ -100,11 +100,14 @@

Source: structures/Payment.js

* The note sent with the payment * @type {string} */ - this.paymentNote = !data.paymentNoteMsg ? undefined : data.paymentNoteMsg.body ? data.paymentNoteMsg.body : undefined ; + this.paymentNote = !data.paymentNoteMsg + ? undefined + : data.paymentNoteMsg.body + ? data.paymentNoteMsg.body + : undefined; return super._patch(data); } - } module.exports = Payment; @@ -118,7 +121,7 @@

Source: structures/Payment.js

diff --git a/docs/structures_Poll.js.html b/docs/structures_Poll.js.html index 4a217bfa838..dd392ae6d58 100644 --- a/docs/structures_Poll.js.html +++ b/docs/structures_Poll.js.html @@ -58,7 +58,7 @@

Source: structures/Poll.js

*/ this.pollOptions = pollOptions.map((option, index) => ({ name: option.trim(), - localId: index + localId: index, })); /** @@ -67,7 +67,7 @@

Source: structures/Poll.js

*/ this.options = { allowMultipleAnswers: options.allowMultipleAnswers === true, - messageSecret: options.messageSecret + messageSecret: options.messageSecret, }; } } @@ -83,7 +83,7 @@

Source: structures/Poll.js

diff --git a/docs/structures_PollVote.js.html b/docs/structures_PollVote.js.html index 009933d9397..9550bfddc83 100644 --- a/docs/structures_PollVote.js.html +++ b/docs/structures_PollVote.js.html @@ -66,21 +66,24 @@

Source: structures/PollVote.js

* @type {SelectedPollOption[]} */ if (data.selectedOptionLocalIds.length > 0) { - if(data.parentMessage) { // temporary failsafe + if (data.parentMessage) { + // temporary failsafe this.selectedOptions = data.selectedOptionLocalIds.map((e) => ({ - name: data.parentMessage.pollOptions.find((x) => x.localId === e).name, - localId: e + name: data.parentMessage.pollOptions.find( + (x) => x.localId === e, + ).name, + localId: e, })); } else { this.selectedOptions = data.selectedOptionLocalIds.map((e) => ({ name: undefined, - localId: e + localId: e, })); } } else { this.selectedOptions = []; } - + /** * Timestamp the option was selected or deselected at * @type {number} @@ -97,7 +100,7 @@

Source: structures/PollVote.js

* The poll creation message id * @type {Object} */ - this.parentMsgKey = data.parentMsgKey; + this.parentMsgKey = data.parentMsgKey; return super._patch(data); } @@ -114,7 +117,7 @@

Source: structures/PollVote.js

diff --git a/docs/structures_PrivateChat.js.html b/docs/structures_PrivateChat.js.html index 64f9e935940..2f8cb523a06 100644 --- a/docs/structures_PrivateChat.js.html +++ b/docs/structures_PrivateChat.js.html @@ -37,11 +37,10 @@

Source: structures/PrivateChat.js

* Represents a Private Chat on WhatsApp * @extends {Chat} */ -class PrivateChat extends Chat { +class PrivateChat extends Chat {} -} - -module.exports = PrivateChat; +module.exports = PrivateChat; + @@ -51,7 +50,7 @@

Source: structures/PrivateChat.js

diff --git a/docs/structures_PrivateContact.js.html b/docs/structures_PrivateContact.js.html index ec94c28c9a4..7a477ebeb58 100644 --- a/docs/structures_PrivateContact.js.html +++ b/docs/structures_PrivateContact.js.html @@ -37,11 +37,10 @@

Source: structures/PrivateContact.js

* Represents a Private Contact on WhatsApp * @extends {Contact} */ -class PrivateContact extends Contact { +class PrivateContact extends Contact {} -} - -module.exports = PrivateContact; +module.exports = PrivateContact; + @@ -51,7 +50,7 @@

Source: structures/PrivateContact.js

diff --git a/docs/structures_Product.js.html b/docs/structures_Product.js.html index c6210fdddb0..1d02e1cc9d9 100644 --- a/docs/structures_Product.js.html +++ b/docs/structures_Product.js.html @@ -96,7 +96,8 @@

Source: structures/Product.js

} } -module.exports = Product; +module.exports = Product; + @@ -106,7 +107,7 @@

Source: structures/Product.js

diff --git a/docs/structures_ProductMetadata.js.html b/docs/structures_ProductMetadata.js.html index e9ce59ec478..2b0a19e9b5f 100644 --- a/docs/structures_ProductMetadata.js.html +++ b/docs/structures_ProductMetadata.js.html @@ -50,10 +50,10 @@

Source: structures/ProductMetadata.js

return super._patch(data); } - } -module.exports = ProductMetadata; +module.exports = ProductMetadata; + @@ -63,7 +63,7 @@

Source: structures/ProductMetadata.js

diff --git a/docs/structures_Reaction.js.html b/docs/structures_Reaction.js.html index a1eed42810d..b5b3afdcecf 100644 --- a/docs/structures_Reaction.js.html +++ b/docs/structures_Reaction.js.html @@ -90,14 +90,13 @@

Source: structures/Reaction.js

* @type {?number} */ this.ack = data.ack; - - + return super._patch(data); } - } -module.exports = Reaction; +module.exports = Reaction; + @@ -107,7 +106,7 @@

Source: structures/Reaction.js

diff --git a/docs/structures_ScheduledEvent.js.html b/docs/structures_ScheduledEvent.js.html index 35544aa916d..d9a2ef0694e 100644 --- a/docs/structures_ScheduledEvent.js.html +++ b/docs/structures_ScheduledEvent.js.html @@ -68,11 +68,13 @@

Source: structures/ScheduledEvent.js

*/ this.eventSendOptions = { description: options.description?.trim(), - endTimeTs: options.endTime ? Math.floor(options.endTime.getTime() / 1000) : null, + endTimeTs: options.endTime + ? Math.floor(options.endTime.getTime() / 1000) + : null, location: options.location?.trim(), callType: this._validateInputs('callType', options.callType), isEventCanceled: options.isEventCanceled ?? false, - messageSecret: options.messageSecret + messageSecret: options.messageSecret, }; } @@ -84,17 +86,27 @@

Source: structures/ScheduledEvent.js

*/ _validateInputs(propName, propValue) { if (propName === 'name' && !propValue) { - throw new class CreateScheduledEventError extends Error { - constructor(m) { super(m); } - }(`Empty '${propName}' parameter value is provided.`); + throw new (class CreateScheduledEventError extends Error { + constructor(m) { + super(m); + } + })(`Empty '${propName}' parameter value is provided.`); } - if (propName === 'callType' && propValue && !['video', 'voice', 'none'].includes(propValue)) { - throw new class CreateScheduledEventError extends Error { - constructor(m) { super(m); } - }(`Invalid '${propName}' parameter value is provided. Valid values are: 'voice' | 'video' | 'none'.`); + if ( + propName === 'callType' && + propValue && + !['video', 'voice', 'none'].includes(propValue) + ) { + throw new (class CreateScheduledEventError extends Error { + constructor(m) { + super(m); + } + })( + `Invalid '${propName}' parameter value is provided. Valid values are: 'voice' | 'video' | 'none'.`, + ); } - + return propValue; } } @@ -110,7 +122,7 @@

Source: structures/ScheduledEvent.js

diff --git a/docs/util_Constants.js.html b/docs/util_Constants.js.html index ed925589b4b..3553f76f8cf 100644 --- a/docs/util_Constants.js.html +++ b/docs/util_Constants.js.html @@ -36,7 +36,7 @@

Source: util/Constants.js

exports.DefaultOptions = { puppeteer: { headless: true, - defaultViewport: null + defaultViewport: null, }, webVersion: '2.3000.1017054665', webVersionCache: { @@ -46,7 +46,8 @@

Source: util/Constants.js

qrMaxRetries: 0, takeoverOnConflict: false, takeoverTimeoutMs: 0, - userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36', ffmpegPath: 'ffmpeg', bypassCSP: false, proxyAuthentication: undefined, @@ -65,7 +66,7 @@

Source: util/Constants.js

exports.Status = { INITIALIZING: 0, AUTHENTICATING: 1, - READY: 3 + READY: 3, }; /** @@ -81,6 +82,7 @@

Source: util/Constants.js

CHAT_ARCHIVED: 'chat_archived', MESSAGE_RECEIVED: 'message', MESSAGE_CIPHERTEXT: 'message_ciphertext', + MESSAGE_CIPHERTEXT_FAILED: 'message_ciphertext_failed', MESSAGE_CREATE: 'message_create', MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone', MESSAGE_REVOKED_ME: 'message_revoke_me', @@ -103,7 +105,7 @@

Source: util/Constants.js

BATTERY_CHANGED: 'change_battery', INCOMING_CALL: 'call', REMOTE_SESSION_SAVED: 'remote_session_saved', - VOTE_UPDATE: 'vote_update' + VOTE_UPDATE: 'vote_update', }; /** @@ -179,7 +181,7 @@

Source: util/Constants.js

exports.ChatTypes = { SOLO: 'solo', GROUP: 'group', - UNKNOWN: 'unknown' + UNKNOWN: 'unknown', }; /** @@ -199,7 +201,7 @@

Source: util/Constants.js

TOS_BLOCK: 'TOS_BLOCK', UNLAUNCHED: 'UNLAUNCHED', UNPAIRED: 'UNPAIRED', - UNPAIRED_IDLE: 'UNPAIRED_IDLE' + UNPAIRED_IDLE: 'UNPAIRED_IDLE', }; /** @@ -225,7 +227,7 @@

Source: util/Constants.js

diff --git a/docs/util_Injected_Utils.js.html b/docs/util_Injected_Utils.js.html index 73e2c99c10d..6d4c3a48c8b 100644 --- a/docs/util_Injected_Utils.js.html +++ b/docs/util_Injected_Utils.js.html @@ -4,7 +4,7 @@ - whatsapp-web.js 1.28.1-alpha.0 » Source: util/Injected/Utils.js + whatsapp-web.js 1.34.6 » Source: util/Injected/Utils.js @@ -15,7 +15,7 @@ @@ -34,78 +34,209 @@

Source: util/Injected/Utils.js

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); + /** + * 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.WWebJS.compareWwebVersions = (lOperand, operator, rOperand) => { + if (!['>', '>=', '<', '<=', '='].includes(operator)) { + throw new (class _ extends Error { + constructor(m) { + super(m); + this.name = 'CompareWwebVersionsError'; + } + })('Invalid comparison operator is provided'); + } + if (typeof lOperand !== 'string' || typeof rOperand !== 'string') { + throw new (class _ extends Error { + constructor(m) { + super(m); + this.name = 'CompareWwebVersionsError'; + } + })('A non-string WWeb version type is provided'); + } - if (window.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) { - return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], true); - } else { - return chat.forwardMessages([msg]); + lOperand = lOperand.replace(/-beta$/, ''); + rOperand = rOperand.replace(/-beta$/, ''); + + while (lOperand.length !== rOperand.length) { + lOperand.length > rOperand.length + ? (rOperand = rOperand.concat('0')) + : (lOperand = lOperand.concat('0')); } + + lOperand = Number(lOperand.replace(/\./g, '')); + rOperand = Number(rOperand.replace(/\./g, '')); + + return operator === '>' + ? lOperand > rOperand + : operator === '>=' + ? lOperand >= rOperand + : operator === '<' + ? lOperand < rOperand + : operator === '<=' + ? lOperand <= rOperand + : operator === '=' + ? lOperand === rOperand + : false; + }; + + /** + * 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.WWebJS.injectToFunction = (target, callback) => { + try { + let module = window.require(target.module); + if (!module) return; + + const path = target.function.split('.'); + const funcName = path.pop(); + + for (const key of path) { + if (!module[key]) return; + module = module[key]; + } + + const originalFunction = module[funcName]; + if (typeof originalFunction !== 'function') return; + + module[funcName] = ((...args) => { + try { + return callback(module, originalFunction, ...args); + } catch { + return originalFunction.apply(module, args); + } + }).bind(module); + } catch { + return; + } + }; + + window.WWebJS.injectToFunction( + { module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, + (module, func, ...args) => { + const [proto] = args; + return proto.locationMessage ? null : func(...args); + }, + ); + + window.WWebJS.injectToFunction( + { module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, + (module, func, ...args) => { + const [proto] = args; + return proto.locationMessage || proto.groupInviteMessage + ? 'text' + : func(...args); + }, + ); + + window.WWebJS.forwardMessage = async (chatId, msgId) => { + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); + return await window.require('WAWebChatForwardMessage').forwardMessages({ + chat: chat, + msgs: [msg], + multicast: true, + includeCaption: true, + appendedText: undefined, + }); }; window.WWebJS.sendSeen = async (chatId) => { - let chat = window.Store.Chat.get(chatId); - if (chat !== undefined) { - await window.Store.SendSeen.sendSeen(chat, false); + const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); + if (chat) { + window.require('WAWebStreamModel').Stream.markAvailable(); + await window.require('WAWebUpdateUnreadChatAction').sendSeen({ + chat: chat, + threadId: undefined, + }); + window.require('WAWebStreamModel').Stream.markUnavailable(); 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; + const { getIsNewsletter, getIsBroadcast } = + window.require('WAWebChatGetters'); + const isChannel = getIsNewsletter(chat); + const isStatus = getIsBroadcast(chat); + + const { findLink } = window.require('WALinkify'); + + let mediaOptions = {}; + if (options.media) { + mediaOptions = + options.sendMediaAsSticker && !isChannel && !isStatus + ? await window.WWebJS.processStickerData(options.media) + : await window.WWebJS.processMediaData(options.media, { + forceSticker: options.sendMediaAsSticker, + forceGif: options.sendVideoAsGif, + forceVoice: options.sendAudioAsVoice, + forceDocument: options.sendMediaAsDocument, + forceMediaHd: options.sendMediaAsHd, + sendToChannel: isChannel, + sendToStatus: isStatus, + }); + mediaOptions.caption = options.caption; + content = options.sendMediaAsSticker + ? undefined + : mediaOptions.preview; + mediaOptions.isViewOnce = options.isViewOnce; + delete options.media; delete options.sendMediaAsSticker; } + let quotedMsgOptions = {}; if (options.quotedMessageId) { - let quotedMessage = await window.Store.Msg.getMessagesById([options.quotedMessageId]); - - if (quotedMessage['messages'].length == 1) { - 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(); + let quotedMessage = window + .require('WAWebCollections') + .Msg.get(options.quotedMessageId); + !quotedMessage && + (quotedMessage = ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([options.quotedMessageId]) + )?.messages?.[0]); + if (quotedMessage) { + const ReplyUtils = window.require('WAWebMsgReply'); + const canReply = ReplyUtils + ? ReplyUtils.canReplyMsg(quotedMessage.unsafe()) + : quotedMessage.canReply(); if (canReply) { quotedMsgOptions = quotedMessage.msgContextInfo(chat); } - }else{ - if(!options.ignoreQuoteErrors) { + } else { + if (!options.ignoreQuoteErrors) { throw new Error('Could not get the quoted message.'); } } - + delete options.ignoreQuoteErrors; 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.map((id) => + window.require('WAWebWidFactory').createWid(id), ); options.mentionedJidList = options.mentionedJidList.filter(Boolean); } @@ -113,68 +244,124 @@

Source: util/Injected/Utils.js

if (options.groupMentions) { options.groupMentions = options.groupMentions.map((e) => ({ groupSubject: e.subject, - groupJid: window.Store.WidFactory.createWid(e.id) + groupJid: window.require('WAWebWidFactory').createWid(e.id), })); } let locationOptions = {}; if (options.location) { let { latitude, longitude, description, url } = options.location; - url = window.Store.Validators.findLink(url)?.href; + url = findLink(url)?.href; url && !description && (description = url); locationOptions = { type: 'location', loc: description, lat: latitude, lng: longitude, - clientUrl: url + clientUrl: url, }; delete options.location; } - let _pollOptions = {}; + let pollOptions = {}; if (options.poll) { - const { pollName, pollOptions } = options.poll; - const { allowMultipleAnswers, messageSecret } = options.poll.options; - _pollOptions = { + const { pollName, pollOptions: _pollOptions } = options.poll; + const { allowMultipleAnswers, messageSecret } = + options.poll.options; + pollOptions = { + kind: 'pollCreation', type: 'poll_creation', pollName: pollName, - pollOptions: pollOptions, + pollOptions: _pollOptions, pollSelectableOptionsCount: allowMultipleAnswers ? 0 : 1, messageSecret: - Array.isArray(messageSecret) && messageSecret.length === 32 - ? new Uint8Array(messageSecret) - : window.crypto.getRandomValues(new Uint8Array(32)) + Array.isArray(messageSecret) && messageSecret.length === 32 + ? new Uint8Array(messageSecret) + : window.crypto.getRandomValues(new Uint8Array(32)), }; delete options.poll; } + let eventOptions = {}; + if (options.event) { + const { name, startTimeTs, eventSendOptions } = options.event; + const { messageSecret } = eventSendOptions; + eventOptions = { + type: 'event_creation', + eventName: name, + eventDescription: eventSendOptions.description, + eventStartTime: startTimeTs, + eventEndTime: eventSendOptions.endTimeTs, + eventLocation: eventSendOptions.location && { + degreesLatitude: 0, + degreesLongitude: 0, + name: eventSendOptions.location, + }, + eventJoinLink: + eventSendOptions.callType === 'none' + ? null + : await window + .require('WAWebGenerateEventCallLink') + .createEventCallLink( + startTimeTs, + eventSendOptions.callType, + ), + isEventCanceled: eventSendOptions.isEventCanceled, + messageSecret: + Array.isArray(messageSecret) && messageSecret.length === 32 + ? new Uint8Array(messageSecret) + : window.crypto.getRandomValues(new Uint8Array(32)), + }; + delete options.event; + } + let vcardOptions = {}; if (options.contactCard) { - let contact = window.Store.Contact.get(options.contactCard); + let contact = await window + .require('WAWebCollections') + .Contact.find(options.contactCard); vcardOptions = { - body: window.Store.VCard.vcardFromContactModel(contact).vcard, + body: window + .require('WAWebFrontendVcardUtils') + .vcardFromContactModel(contact).vcard, type: 'vcard', - vcardFormattedName: contact.formattedName + 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)); + let contacts = await Promise.all( + options.contactCardList.map((c) => + window.require('WAWebCollections').Contact.find(c), + ), + ); + let vcards = contacts.map((c) => + window + .require('WAWebFrontendVcardUtils') + .vcardFromContactModel(c), + ); vcardOptions = { type: 'multi_vcard', vcardList: vcards, - body: undefined + body: null, }; delete options.contactCardList; - } else if (options.parseVCards && typeof (content) === 'string' && content.startsWith('BEGIN:VCARD')) { + } else if ( + options.parseVCards && + typeof content === 'string' && + content.startsWith('BEGIN:VCARD') + ) { delete options.parseVCards; + delete options.linkPreview; try { - const parsed = window.Store.VCard.parseVcard(content); + const parsed = window + .require('WAWebVcardParsingUtils') + .parseVcard(content); if (parsed) { vcardOptions = { type: 'vcard', - vcardFormattedName: window.Store.VCard.vcardGetNameFromParsed(parsed) + vcardFormattedName: window + .require('WAWebVcardGetNameFromParsed') + .vcardGetNameFromParsed(parsed), }; } } catch (_) { @@ -184,20 +371,22 @@

Source: util/Injected/Utils.js

if (options.linkPreview) { delete options.linkPreview; - const link = window.Store.Validators.findLink(content); + const link = findLink(content); if (link) { - let preview = await window.Store.LinkPreview.getLinkPreview(link); + let preview = await window + .require('WAWebLinkPreviewChatAction') + .getLinkPreview(link); if (preview && preview.data) { preview = preview.data; preview.preview = true; preview.subtype = 'url'; - options = {...options, ...preview}; + options = { ...options, ...preview }; } } } - + let buttonOptions = {}; - if(options.buttons){ + if (options.buttons) { let caption; if (options.buttons.type === 'chat') { content = options.buttons.body; @@ -209,28 +398,35 @@

Source: util/Injected/Utils.js

productHeaderImageRejected: false, isFromTemplate: false, isDynamicReplyButtonsMsg: true, - title: options.buttons.title ? options.buttons.title : undefined, - footer: options.buttons.footer ? options.buttons.footer : undefined, + 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 + 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'; + if ( + window.require('WAWebConnModel').Conn.platform === 'smba' || + window.require('WAWebConnModel').Conn.platform === 'smbi' + ) { + throw "[LT01] Whatsapp business can't send this yet"; } listOptions = { type: 'list', footer: options.list.footer, list: { ...options.list, - listType: 1 + listType: 1, }, - body: options.list.description + body: options.list.description, }; delete options.list; delete listOptions.list.footer; @@ -238,39 +434,66 @@

Source: util/Injected/Utils.js

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; + botOptions.messageSecret = window.crypto.getRandomValues( + new Uint8Array(32), + ); + botOptions.botMessageSecret = await window + .require('WAWebBotMessageSecret') + .genBotMsgSecretFromMsgSecret(botOptions.messageSecret); + botOptions.invokedBotWid = window + .require('WAWebWidFactory') + .createWid(options.invokedBotWid); + botOptions.botPersonaId = window + .require('WAWebBotProfileCollection') + .BotProfileCollection.get(options.invokedBotWid).personaId; delete options.invokedBotWid; } + const { getMaybeMeLidUser, getMaybeMePnUser } = window.require( + 'WAWebUserPrefsMeUser', + ); + const lidUser = getMaybeMeLidUser(); + const meUser = getMaybeMePnUser(); + const newId = await window.require('WAWebMsgKey').newId(); + let from = chat.id.isLid() ? lidUser : meUser; + let participant; + + if (typeof chat.id?.isGroup === 'function' && chat.id.isGroup()) { + from = + chat.groupMetadata && chat.groupMetadata.isLidAddressingMode + ? lidUser + : meUser; + participant = window + .require('WAWebWidFactory') + .asUserWidOrThrow(from); + } - let meUser = window.Store.User.getMaybeMeUser(); - const newId = await window.Store.MsgKey.newId(); - - if (chat.id.isGroup() && chat.groupMetadata.isLidAddressingMode) { - meUser = window.Store.User.getMaybeMeLidUser(); + if (typeof chat.id?.isStatus === 'function' && chat.id.isStatus()) { + participant = window + .require('WAWebWidFactory') + .asUserWidOrThrow(from); } - - const newMsgId = new window.Store.MsgKey({ - from: meUser, + + const newMsgKey = new (window.require('WAWebMsgKey'))({ + from: from, to: chat.id, id: newId, - participant: chat.id.isGroup() ? meUser : undefined, + participant: participant, selfDir: 'out', }); const extraOptions = options.extraOptions || {}; delete options.extraOptions; - const ephemeralFields = window.Store.EphemeralFields.getEphemeralFields(chat); + const ephemeralFields = window + .require('WAWebGetEphemeralFieldsMsgActionsUtils') + .getEphemeralFields(chat); const message = { ...options, - id: newMsgId, + id: newMsgKey, ack: 0, body: content, - from: meUser, + from: from, to: chat.id, local: true, self: 'out', @@ -278,40 +501,128 @@

Source: util/Injected/Utils.js

isNewMsg: true, type: 'chat', ...ephemeralFields, - ...locationOptions, - ..._pollOptions, - ...attOptions, - ...(attOptions.toJSON ? attOptions.toJSON() : {}), + ...mediaOptions, + ...(mediaOptions.toJSON ? mediaOptions.toJSON() : {}), ...quotedMsgOptions, + ...locationOptions, + ...pollOptions, + ...eventOptions, ...vcardOptions, ...buttonOptions, ...listOptions, ...botOptions, - ...extraOptions + ...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); + if (isChannel) { + const msg = new (window.require('WAWebCollections').Msg.modelClass)( + message, + ); + const msgDataFromMsgModel = window + .require('WAWebMsgDataFromModel') + .msgDataFromMsgModel(msg); + const isMedia = Object.keys(mediaOptions).length > 0; + await window + .require('WAWebNewsletterUpdateMsgsRecordsJob') + .addNewsletterMsgsRecords([msgDataFromMsgModel]); + chat.msgs.add(msg); + chat.t = msg.t; + + const sendChannelMsgResponse = await window + .require('WAWebNewsletterSendMessageJob') + .sendNewsletterMessageJob({ + msg: msg, + type: + message.type === 'chat' + ? 'text' + : isMedia + ? 'media' + : 'pollCreation', + newsletterJid: chat.id.toJid(), + ...(isMedia + ? { + mediaMetadata: msg.avParams(), + mediaHandle: isMedia + ? mediaOptions.mediaHandle + : null, + } + : {}), + }); + + if (sendChannelMsgResponse.success) { + msg.t = sendChannelMsgResponse.ack.t; + msg.serverId = sendChannelMsgResponse.serverId; + } + msg.updateAck(1, true); + await window + .require('WAWebNewsletterUpdateMsgsRecordsJob') + .updateNewsletterMsgRecord(msg); + return msg; + } + + if (isStatus) { + const { backgroundColor, fontStyle } = extraOptions; + const isMedia = Object.keys(mediaOptions).length > 0; + const mediaUpdate = (data) => + window.require('WAWebMediaUpdateMsg')(data, mediaOptions); + const msg = new (window.require('WAWebCollections').Msg.modelClass)( + { + ...message, + author: participant ? participant : null, + messageSecret: window.crypto.getRandomValues( + new Uint8Array(32), + ), + cannotBeRanked: window + .require('WAWebStatusGatingUtils') + .canCheckStatusRankingPosterGating(), + }, + ); + + // for text only + const statusOptions = { + color: + (backgroundColor && + window.WWebJS.assertColor(backgroundColor)) || + 0xff7acca5, + font: (fontStyle >= 0 && fontStyle <= 7 && fontStyle) || 0, + text: msg.body, + }; + + await window + .require('WAWebSendStatusMsgAction') + [ + isMedia + ? 'sendStatusMediaMsgAction' + : 'sendStatusTextMsgAction' + ](...(isMedia ? [msg, mediaUpdate] : [statusOptions])); + + return msg; + } + + const [msgPromise, sendMsgResultPromise] = window + .require('WAWebSendMsgChatAction') + .addAndSendMsgToChat(chat, message); + await msgPromise; + + if (options.waitUntilMsgSent) await sendMsgResultPromise; + + return window + .require('WAWebCollections') + .Msg.get(newMsgKey._serialized); }; - - window.WWebJS.editMessage = async (msg, content, options = {}) => { + 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.map((id) => + window.require('WAWebWidFactory').createWid(id), ); options.mentionedJidList = options.mentionedJidList.filter(Boolean); } @@ -319,59 +630,73 @@

Source: util/Injected/Utils.js

if (options.groupMentions) { options.groupMentions = options.groupMentions.map((e) => ({ groupSubject: e.subject, - groupJid: window.Store.WidFactory.createWid(e.id) + groupJid: window.require('WAWebWidFactory').createWid(e.id), })); } if (options.linkPreview) { + const { findLink } = window.require('WALinkify'); delete options.linkPreview; - const link = window.Store.Validators.findLink(content); + const link = findLink(content); if (link) { - const preview = await window.Store.LinkPreview.getLinkPreview(link); + const preview = await window + .require('WAWebLinkPreviewChatAction') + .getLinkPreview(link); preview.preview = true; preview.subtype = 'url'; options = { ...options, ...preview }; } } - const internalOptions = { ...options, - ...extraOptions + ...extraOptions, }; - await window.Store.EditMessage.sendMessageEdit(msg, content, internalOptions); - return window.Store.Msg.get(msg.id._serialized); + await window + .require('WAWebSendMessageEditAction') + .sendMessageEdit(msg, content, internalOptions); + return window.require('WAWebCollections').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 webpSticker = await window + .require('WAWebImageUtils') + .toWebpSticker(file); const webpBuffer = await webpSticker.arrayBuffer(); const data = window.WWebJS.arrayBufferToBase64(webpBuffer); return { mimetype: 'image/webp', - data + data, }; }; window.WWebJS.processStickerData = async (mediaInfo) => { - if (mediaInfo.mimetype !== 'image/webp') throw new Error('Invalid media type'); + 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 uploadedInfo = await window + .require('WAWebUploadManager') + .encryptAndUpload({ + blob: file, + type: 'sticker', + signal: controller.signal, + mediaKey, + uploadQpl: window + .require('WAWebStartMediaUploadQpl') + .startMediaUploadQpl({ + entryPoint: 'MediaUpload', + }), + }); const stickerInfo = { ...uploadedInfo, @@ -380,52 +705,108 @@

Source: util/Injected/Utils.js

uploadhash: uploadedInfo.encFilehash, size: file.size, type: 'sticker', - filehash + filehash, }; return stickerInfo; }; - window.WWebJS.processMediaData = async (mediaInfo, { forceVoice, forceDocument, forceGif }) => { + window.WWebJS.processMediaData = async ( + mediaInfo, + { + forceSticker, + forceGif, + forceVoice, + forceDocument, + forceMediaHd, + sendToChannel, + sendToStatus, + }, + ) => { 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 OpaqueData = window.require('WAWebMediaOpaqueData'); + const opaqueData = await OpaqueData.createFromData( + file, + mediaInfo.mimetype, + ); + const mediaParams = { + asSticker: forceSticker, + asGif: forceGif, + isPtt: forceVoice, + asDocument: forceDocument, + }; + + if (forceMediaHd && file.type.indexOf('image/') === 0) { + mediaParams.maxDimension = 2560; + } - const mediaType = window.Store.MediaTypes.msgToMediaType({ + const mediaPrep = window + .require('WAWebPrepRawMedia') + .prepRawMedia(opaqueData, mediaParams); + const mediaData = await mediaPrep.waitForPrep(); + const mediaObject = window + .require('WAWebMediaStorage') + .getOrCreateMediaObject(mediaData.filehash); + const mediaType = window.require('WAWebMmsMediaTypes').msgToMediaType({ type: mediaData.type, - isGif: mediaData.isGif + isGif: mediaData.isGif, + isNewsletter: sendToChannel, }); - if (forceVoice && mediaData.type === 'audio') { - mediaData.type = 'ptt'; - const waveform = mediaObject.contentInfo.waveform; - mediaData.waveform = - waveform ?? await window.WWebJS.generateWaveform(file); - } - - if (forceGif && mediaData.type === 'video') { - mediaData.isGif = true; + if (!mediaData.filehash) { + throw new Error('media-fault: sendToChat filehash undefined'); } - if (forceDocument) { - mediaData.type = 'document'; + if ( + (forceVoice && mediaData.type === 'ptt') || + (sendToStatus && mediaData.type === 'audio') + ) { + const waveform = mediaObject.contentInfo.waveform; + mediaData.waveform = + waveform || (await window.WWebJS.generateWaveform(file)); } - if (!(mediaData.mediaBlob instanceof window.Store.OpaqueData)) { - mediaData.mediaBlob = await window.Store.OpaqueData.createFromData(mediaData.mediaBlob, mediaData.mediaBlob.type); + if (!(mediaData.mediaBlob instanceof OpaqueData)) { + mediaData.mediaBlob = await OpaqueData.createFromData( + mediaData.mediaBlob, + mediaData.mediaBlob.type, + ); } mediaData.renderableUrl = mediaData.mediaBlob.url(); mediaObject.consolidate(mediaData.toJSON()); + mediaData.mediaBlob.autorelease(); + const shouldUseMediaCache = window + .require('WAWebMediaDataUtils') + .shouldUseMediaCache( + window.require('WAWebMmsMediaTypes').castToV4(mediaObject.type), + ); + if (shouldUseMediaCache && mediaData.mediaBlob instanceof OpaqueData) { + const formData = mediaData.mediaBlob.formData(); + window + .require('WAWebMediaInMemoryBlobCache') + .InMemoryMediaBlobCache.put(mediaObject.filehash, formData); + } - const uploadedMedia = await window.Store.MediaUpload.uploadMedia({ + const dataToUpload = { mimetype: mediaData.mimetype, mediaObject, - mediaType - }); + mediaType, + ...(sendToChannel + ? { + calculateToken: window.require('WAMediaCalculateFilehash') + .getRandomFilehash, + } + : {}), + }; + + const { uploadMedia, uploadUnencryptedMedia } = window.require( + 'WAWebMediaMmsV4Upload', + ); + const uploadedMedia = !sendToChannel + ? await uploadMedia(dataToUpload) + : await uploadUnencryptedMedia(dataToUpload); const mediaEntry = uploadedMedia.mediaEntry; if (!mediaEntry) { @@ -443,34 +824,45 @@

Source: util/Injected/Utils.js

uploadhash: mediaEntry.uploadHash, size: mediaObject.size, streamingSidecar: mediaEntry.sidecar, - firstFrameSidecar: mediaEntry.firstFrameSidecar + firstFrameSidecar: mediaEntry.firstFrameSidecar, + mediaHandle: sendToChannel ? mediaEntry.handle : null, }); return mediaData; }; - window.WWebJS.getMessageModel = message => { + window.WWebJS.getMessageModel = (message) => { const msg = message.serialize(); + const { findLinks } = window.require('WALinkify'); + msg.isEphemeral = message.isEphemeral; msg.isStatusV3 = message.isStatusV3; - msg.links = (window.Store.Validators.findLinks(message.mediaObject ? message.caption : message.body)).map((link) => ({ + msg.links = findLinks( + message.mediaObject ? message.caption : message.body, + ).map((link) => ({ link: link.href, - isSuspicious: Boolean(link.suspiciousCharacters && link.suspiciousCharacters.size) + isSuspicious: Boolean( + link.suspiciousCharacters && link.suspiciousCharacters.size, + ), })); if (msg.buttons) { msg.buttons = msg.buttons.serialize(); } if (msg.dynamicReplyButtons) { - msg.dynamicReplyButtons = JSON.parse(JSON.stringify(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 }); + msg.id = Object.assign({}, msg.id, { + remote: msg.id.remote._serialized, + }); } delete msg.pendingAckUpdate; @@ -478,127 +870,260 @@

Source: util/Injected/Utils.js

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 && (_vote.parentMessage = window.WWebJS.getMessageModel(msg)); - return _vote; + window.WWebJS.getChat = async (chatId, { getAsModel = true } = {}) => { + const isChannel = /@\w*newsletter\b/.test(chatId); + const chatWid = window.require('WAWebWidFactory').createWid(chatId); + let chat; + + if (isChannel) { + try { + chat = window + .require('WAWebCollections') + .WAWebNewsletterCollection.get(chatId); + if (!chat) { + await window + .require('WAWebLoadNewsletterPreviewChatAction') + .loadNewsletterPreviewChat(chatId); + chat = await window + .require('WAWebCollections') + .WAWebNewsletterCollection.find(chatWid); + } + } catch (err) { + chat = null; + } + } else { + chat = + window.require('WAWebCollections').Chat.get(chatWid) || + ( + await window + .require('WAWebFindChatAction') + .findOrCreateLatestChat(chatWid) + )?.chat; + } + + return getAsModel && chat + ? await window.WWebJS.getChatModel(chat, { isChannel: isChannel }) + : chat; }; - window.WWebJS.getChatModel = async chat => { + window.WWebJS.getChannelMetadata = async (inviteCode) => { + const role = window + .require('WAWebNewsletterModelUtils') + .getRoleByIdentifier(inviteCode); + const response = await window + .require('WAWebNewsletterMetadataQueryJob') + .queryNewsletterMetadataByInviteCode(inviteCode, role); - let res = chat.serialize(); - res.isGroup = false; - res.formattedTitle = chat.formattedTitle; - res.isMuted = chat.mute?.expiration !== 0; + const picUrl = + response.newsletterPictureMetadataMixin?.picture[0] + ?.queryPictureDirectPathOrEmptyResponseMixinGroup.value + .directPath; + + return { + id: response.idJid, + createdAtTs: + response.newsletterCreationTimeMetadataMixin.creationTimeValue, + titleMetadata: { + title: response.newsletterNameMetadataMixin.nameElementValue, + updatedAtTs: + response.newsletterNameMetadataMixin.nameUpdateTime, + }, + descriptionMetadata: { + description: + response.newsletterDescriptionMetadataMixin + .descriptionQueryDescriptionResponseMixin.elementValue, + updatedAtTs: + response.newsletterDescriptionMetadataMixin + .descriptionQueryDescriptionResponseMixin.updateTime, + }, + inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`, + membershipType: role, + stateType: response.newsletterStateMetadataMixin.stateType, + pictureUrl: picUrl ? `https://pps.whatsapp.net${picUrl}` : null, + subscribersCount: + response.newsletterSubscribersMetadataMixin.subscribersCount, + isVerified: + response.newsletterVerificationMetadataMixin + .verificationState === 'verified', + }; + }; + + window.WWebJS.getChats = async () => { + const chats = window.require('WAWebCollections').Chat.getModelsArray(); + const chatPromises = chats.map((chat) => + window.WWebJS.getChatModel(chat), + ); + return await Promise.all(chatPromises); + }; + + window.WWebJS.getChannels = async () => { + const channels = window + .require('WAWebCollections') + .WAWebNewsletterCollection.getModelsArray(); + const channelPromises = channels?.map((channel) => + window.WWebJS.getChatModel(channel, { isChannel: true }), + ); + return await Promise.all(channelPromises); + }; + + window.WWebJS.getChatModel = async (chat, { isChannel = false } = {}) => { + if (!chat) return null; + + const model = chat.serialize(); + model.isGroup = false; + model.isMuted = chat.mute?.expiration !== 0; + if (isChannel) { + model.isChannel = window + .require('WAWebChatGetters') + .getIsNewsletter(chat); + } else { + model.formattedTitle = chat.formattedTitle; + } if (chat.groupMetadata) { - res.isGroup = true; - const chatWid = window.Store.WidFactory.createWid((chat.id._serialized)); - await window.Store.GroupMetadata.update(chatWid); - res.groupMetadata = chat.groupMetadata.serialize(); - res.isReadOnly = chat.groupMetadata.announce; + model.isGroup = true; + const chatWid = window + .require('WAWebWidFactory') + .createWid(chat.id._serialized); + const groupMetadata = + window.require('WAWebCollections').GroupMetadata || + window.require('WAWebCollections').WAWebGroupMetadataCollection; + await groupMetadata.update(chatWid); + const { toPn } = window.require('WAWebLidMigrationUtils'); + const serializedMetadata = chat.groupMetadata.serialize(); + for (const p of serializedMetadata.participants || []) { + p.id = toPn(p.id) ?? p.id; + } + model.groupMetadata = serializedMetadata; + model.isReadOnly = chat.groupMetadata.announce; } - - res.lastMessage = null; - if (res.msgs && res.msgs.length) { + + if (chat.newsletterMetadata) { + const newsletterMetadata = + window.require('WAWebCollections') + .NewsletterMetadataCollection || + window.require('WAWebCollections') + .WAWebNewsletterMetadataCollection; + await newsletterMetadata.update(chat.id); + model.channelMetadata = chat.newsletterMetadata.serialize(); + model.channelMetadata.createdAtTs = + chat.newsletterMetadata.creationTime; + } + + model.lastMessage = null; + if (model.msgs && model.msgs.length) { const lastMessage = chat.lastReceivedKey - ? window.Store.Msg.get(chat.lastReceivedKey._serialized) || (await window.Store.Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0] + ? window + .require('WAWebCollections') + .Msg.get(chat.lastReceivedKey._serialized) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([ + chat.lastReceivedKey._serialized, + ]) + )?.messages?.[0] : null; - if (lastMessage) { - res.lastMessage = window.WWebJS.getMessageModel(lastMessage); - } + lastMessage && + (model.lastMessage = + window.WWebJS.getMessageModel(lastMessage)); } - - delete res.msgs; - delete res.msgUnsyncedButtonReplyMsgs; - delete res.unsyncedButtonReplies; - return res; - }; + delete model.msgs; + delete model.msgUnsyncedButtonReplyMsgs; + delete model.unsyncedButtonReplies; - 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); + return model; }; - window.WWebJS.getChats = async () => { - const chats = window.Store.Chat.getModelsArray(); + window.WWebJS.getContactModel = (contact) => { + let res = contact.serialize(); - const chatPromises = chats.map(chat => window.WWebJS.getChatModel(chat)); - return await Promise.all(chatPromises); - }; + const wid = window + .require('WAWebWidFactory') + .createWidFromWidLike(contact.id); + if (wid.isLid() && contact.phoneNumber) { + res.id = contact.phoneNumber; + } - window.WWebJS.getContactModel = contact => { - let res = contact.serialize(); - res.isBusiness = contact.isBusiness === undefined ? false : contact.isBusiness; + 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, '<', '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); + if (!res.isBlocked) { + const alt = window + .require('WAWebApiContact') + .getAlternateUserWid( + window + .require('WAWebWidFactory') + .asUserWidOrThrow(contact.id), + ); + if (alt) { + res.isBlocked = !!window + .require('WAWebCollections') + .Blocklist.get(alt); + } + } + + const ContactMethods = window.require('WAWebContactGetters'); + res.isMe = ContactMethods.getIsMe(contact); + res.isUser = ContactMethods.getIsUser(contact); + res.isGroup = ContactMethods.getIsGroup(contact); + res.isWAContact = ContactMethods.getIsWAContact(contact); + res.userid = ContactMethods.getUserid(contact); + res.verifiedName = ContactMethods.getVerifiedName(contact); + res.verifiedLevel = ContactMethods.getVerifiedLevel(contact); + res.statusMute = ContactMethods.getStatusMute(contact); + res.name = ContactMethods.getName(contact); + res.shortName = ContactMethods.getShortName(contact); + res.pushname = ContactMethods.getPushname(contact); + + const { getIsMyContact } = window.require( + 'WAWebFrontendContactGetters', + ); + res.isMyContact = getIsMyContact(contact); + res.isEnterprise = ContactMethods.getIsEnterprise(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 && (contact.businessProfile = bizProfile); + window.WWebJS.getContact = async (contactId) => { + const contactWid = window + .require('WAWebWidFactory') + .createWid(contactId); + const contact = await window + .require('WAWebCollections') + .Contact.find(contactWid); + if (contact.isBusiness || contact.isEnterprise) { + const bizProfile = await window + .require('WAWebCollections') + .BusinessProfile.find(contactWid); + bizProfile.profileOptions && (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)); + const contacts = window + .require('WAWebCollections') + .Contact.getModelsArray(); + return contacts.map(async (contact) => { + if (contact.isBusiness || contact.isEnterprise) { + const contactWid = window + .require('WAWebWidFactory') + .createWid(contact.id); + const bizProfile = await window + .require('WAWebCollections') + .BusinessProfile.find(contactWid); + bizProfile.profileOptions && + (contact.businessProfile = bizProfile); + } + return window.WWebJS.getContactModel(contact); + }); }; window.WWebJS.mediaInfoToFile = ({ data, mimetype, filename }) => { @@ -613,7 +1138,7 @@

Source: util/Injected/Utils.js

const blob = new Blob([buffer], { type: mimetype }); return new File([blob], filename, { type: mimetype, - lastModified: Date.now() + lastModified: Date.now(), }); }; @@ -649,18 +1174,17 @@

Source: util/Injected/Utils.js

window.WWebJS.generateHash = async (length) => { var result = ''; - var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var characters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; for (var i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); + 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(); @@ -684,7 +1208,7 @@

Source: util/Injected/Utils.js

const normalizedData = filteredData.map((n) => n * multiplier); const waveform = new Uint8Array( - normalizedData.map((n) => Math.floor(100 * n)) + normalizedData.map((n) => Math.floor(100 * n)), ); return waveform; @@ -694,44 +1218,45 @@

Source: util/Injected/Utils.js

}; window.WWebJS.sendClearChat = async (chatId) => { - let chat = window.Store.Chat.get(chatId); + let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); if (chat !== undefined) { - await window.Store.SendClear.sendClear(chat, false); + await window.require('WAWebChatClearBridge').sendClear(chat, false); return true; } return false; }; window.WWebJS.sendDeleteChat = async (chatId) => { - let chat = window.Store.Chat.get(chatId); + let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); if (chat !== undefined) { - await window.Store.SendDelete.sendDelete(chat); + await window.require('WAWebDeleteChatAction').sendDelete(chat); return true; } return false; }; window.WWebJS.sendChatstate = async (state, chatId) => { - chatId = window.Store.WidFactory.createWid(chatId); + chatId = window.require('WAWebWidFactory').createWid(chatId); + const ChatState = window.require('WAWebChatStateBridge'); 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'; + case 'typing': + await ChatState.sendChatStateComposing(chatId); + break; + case 'recording': + await ChatState.sendChatStateRecording(chatId); + break; + case 'stop': + await ChatState.sendChatStatePaused(chatId); + break; + default: + throw 'Invalid chatstate'; } return true; }; - window.WWebJS.getLabelModel = label => { + window.WWebJS.getLabelModel = (label) => { let res = label.serialize(); res.hexColor = label.hexColor; @@ -739,28 +1264,34 @@

Source: util/Injected/Utils.js

}; window.WWebJS.getLabels = () => { - const labels = window.Store.Label.getModelsArray(); - return labels.map(label => window.WWebJS.getLabelModel(label)); + const labels = window + .require('WAWebCollections') + .Label.getModelsArray(); + return labels.map((label) => window.WWebJS.getLabelModel(label)); }; window.WWebJS.getLabel = (labelId) => { - const label = window.Store.Label.get(labelId); + const label = window.require('WAWebCollections').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)); + 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); + const chatWid = window.require('WAWebWidFactory').createWid(chatId); + return window + .require('WAWebBizOrderBridge') + .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); + let sellerId = window.require('WAWebConnModel').Conn.wid; + let product = await window + .require('WAWebBizProductCatalogBridge') + .queryProduct(sellerId, productId); if (product && product.data) { return product.data; } @@ -769,20 +1300,26 @@

Source: util/Injected/Utils.js

}; 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); + let userId = window + .require('WAWebUserPrefsMeUser') + .getMaybeMePnUser()._serialized; + + const stanza = window.require('WAWap').wap( + 'call', + { + id: window.require('WAWap').generateId(), + from: userId, + to: peerJid, + }, + [ + window.require('WAWap').wap('reject', { + 'call-id': id, + 'call-creator': peerJid, + count: '0', + }), + ], + ); + await window.require('WADeprecatedSendIq').deprecatedCastStanza(stanza); }; window.WWebJS.cropAndResizeImage = async (media, options = {}) => { @@ -792,9 +1329,17 @@

Source: util/Injected/Utils.js

if (options.mimetype && !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) => { + options = Object.assign( + { + size: 640, + mimetype: media.mimetype, + quality: 0.75, + asDataUrl: false, + }, + options, + ); + + const img = await new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; @@ -814,48 +1359,69 @@

Source: util/Injected/Utils.js

const dataUrl = canvas.toDataURL(options.mimetype, options.quality); - if (options.asDataUrl) - return dataUrl; + if (options.asDataUrl) return dataUrl; return Object.assign(media, { - mimetype: options.mimeType, - data: dataUrl.replace(`data:${options.mimeType};base64,`, '') + 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 }); + 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); + const chatWid = window.require('WAWebWidFactory').createWid(chatId); try { - const collection = window.Store.ProfilePicThumb.get(chatid); - if (!collection.canSet()) return; - - const res = await window.Store.GroupUtils.sendSetPicture(chatWid, thumbnail, profilePic); + const collection = + window + .require('WAWebCollections') + .ProfilePicThumb.get(chatId) || + (await window + .require('WAWebCollections') + .ProfilePicThumb.find(chatId)); + if (!collection?.canSet()) return false; + + const res = await window + .require('WAWebContactProfilePicThumbBridge') + .sendSetPicture(chatWid, thumbnail, profilePic); return res ? res.status === 200 : false; } catch (err) { - if(err.name === 'ServerStatusCodeError') return false; + if (err.name === 'ServerStatusCodeError') return false; throw err; } }; window.WWebJS.deletePicture = async (chatid) => { - const chatWid = window.Store.WidFactory.createWid(chatid); + const chatWid = window.require('WAWebWidFactory').createWid(chatid); try { - const collection = window.Store.ProfilePicThumb.get(chatid); + const collection = window + .require('WAWebCollections') + .ProfilePicThumb.get(chatid); if (!collection.canDelete()) return; - const res = await window.Store.GroupUtils.requestDeletePicture(chatWid); + const res = await window + .require('WAWebContactProfilePicThumbBridge') + .requestDeletePicture(chatWid); return res ? res.status === 200 : false; } catch (err) { - if(err.name === 'ServerStatusCodeError') return false; + if (err.name === 'ServerStatusCodeError') return false; throw err; } }; - + window.WWebJS.getProfilePicThumbToBase64 = async (chatWid) => { - const profilePicCollection = await window.Store.ProfilePicThumb.find(chatWid); + const profilePicCollection = await window + .require('WAWebCollections') + .ProfilePicThumb.find(chatWid); const _readImageAsBase64 = (imageBlob) => { return new Promise((resolve) => { @@ -883,69 +1449,60 @@

Source: util/Injected/Utils.js

return base64Image; } } - } catch (error) { /* empty */ } + } 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); + window.WWebJS.getAddParticipantsRpcResult = async ( + groupWid, + participantWid, + ) => { + const iqTo = window.require('WAWebWidToJid').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) - }]; + const participantArgs = [ + { + participantJid: window + .require('WAWebWidToJid') + .widToUserJid(participantWid), + }, + ]; let rpcResult, resultArgs; - const isOldImpl = window.compareWwebVersions(window.Debug.VERSION, '<=', '2.2335.9'); const data = { name: undefined, code: undefined, inviteV4Code: undefined, - inviteV4CodeExp: undefined + inviteV4CodeExp: undefined, }; try { - rpcResult = await window.Store.GroupParticipants.sendAddParticipantsRPC({ participantArgs, iqTo }); - resultArgs = isOldImpl - ? rpcResult.value.addParticipant[0].addParticipantsParticipantMixins - : rpcResult.value.addParticipant[0] + rpcResult = await window + .require('WASmaxGroupsAddParticipantsRPC') + .sendAddParticipantsRPC({ participantArgs, iqTo }); + resultArgs = + rpcResult.value.addParticipant[0] .addParticipantsParticipantAddedOrNonRegisteredWaUserParticipantErrorLidResponseMixinGroup - .value - .addParticipantsParticipantMixins; + .value.addParticipantsParticipantMixins; } catch (err) { data.code = 400; return data; } if (rpcResult.name === 'AddParticipantsResponseSuccess') { - const code = resultArgs?.value.error ?? '200'; + 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.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') { + } else if (rpcResult.name === 'AddParticipantsResponseServerError') { const { code: code } = rpcResult.value.errorServerErrors.value; data.code = +code; } @@ -953,44 +1510,69 @@

Source: util/Injected/Utils.js

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); + window.WWebJS.membershipRequestAction = async ( + groupId, + action, + requesterIds, + sleep, + ) => { + const groupWid = window.require('WAWebWidFactory').createWid(groupId); + const group = await window + .require('WAWebCollections') + .Chat.find(groupWid); const toApprove = action === 'Approve'; let membershipRequests; let response; let result = []; - await window.Store.GroupQueryAndUpdate({ id: groupId }); + await window + .require('WAWebGroupQueryJob') + .queryAndUpdateGroupMetadataById({ id: groupId }); if (!requesterIds?.length) { - membershipRequests = group.groupMetadata.membershipApprovalRequests._models.map(({ id }) => id); + membershipRequests = + group.groupMetadata.membershipApprovalRequests._models.map( + ({ id }) => id, + ); } else { !Array.isArray(requesterIds) && (requesterIds = [requesterIds]); - membershipRequests = requesterIds.map(r => window.Store.WidFactory.createWid(r)); + membershipRequests = requesterIds.map((r) => + window.require('WAWebWidFactory').createWid(r), + ); } if (!membershipRequests.length) return []; - const participantArgs = membershipRequests.map(m => ({ + const participantArgs = membershipRequests.map((m) => ({ participantArgs: [ { - participantJid: window.Store.WidToJid.widToUserJid(m) - } - ] + participantJid: window + .require('WAWebWidToJid') + .widToUserJid(m), + }, + ], })); - const groupJid = window.Store.WidToJid.widToGroupJid(groupWid); - + const groupJid = window + .require('WAWebWidToJid') + .widToGroupJid(groupWid); + const _getSleepTime = (sleep) => { - if (!Array.isArray(sleep) || (sleep.length === 2 && sleep[0] === sleep[1])) { + if ( + !Array.isArray(sleep) || + (sleep.length === 2 && sleep[0] === sleep[1]) + ) { return sleep; } if (sleep.length === 1) { return sleep[0]; } - sleep[1] - sleep[0] < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100); - return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0]; + sleep[1] - sleep[0] < 100 && + (sleep[0] = sleep[1]) && + (sleep[1] += 100); + return ( + Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0] + ); }; const membReqResCodes = { @@ -1002,45 +1584,69 @@

Source: util/Injected/Utils.js

408: 'ParticipantTemporarilyBlockedError', 409: 'ParticipantConflictError', 412: 'ParticipantParentLinkedGroupsResourceConstraintError', - 500: 'ParticipantResourceConstraintError' + 500: 'ParticipantResourceConstraintError', }; try { for (const participant of participantArgs) { - response = await window.Store.MembershipRequestUtils.sendMembershipRequestsActionRPC({ - iqTo: groupJid, - [toApprove ? 'approveArgs' : 'rejectArgs']: participant - }); + response = await window + .require('WASmaxGroupsMembershipRequestsActionRPC') + .sendMembershipRequestsActionRPC({ + iqTo: groupJid, + [toApprove ? 'approveArgs' : 'rejectArgs']: participant, + }); - if (response.name === 'MembershipRequestsActionResponseSuccess') { + if ( + response.name === 'MembershipRequestsActionResponseSuccess' + ) { const value = toApprove ? response.value.membershipRequestsActionApprove : response.value.membershipRequestsActionReject; if (value?.participant) { - const [_] = value.participant.map(p => { + const [_] = value.participant.map((p) => { const error = toApprove - ? value.participant[0].membershipRequestsActionAcceptParticipantMixins?.value.error - : value.participant[0].membershipRequestsActionRejectParticipantMixins?.value.error; + ? value.participant[0] + .membershipRequestsActionAcceptParticipantMixins + ?.value.error + : value.participant[0] + .membershipRequestsActionRejectParticipantMixins + ?.value.error; return { - requesterId: window.Store.WidFactory.createWid(p.jid)._serialized, + requesterId: window + .require('WAWebWidFactory') + .createWid(p.jid)._serialized, ...(error - ? { error: +error, message: membReqResCodes[error] || membReqResCodes.default } - : { message: `${toApprove ? 'Approved' : 'Rejected'} successfully` }) + ? { + error: +error, + message: + membReqResCodes[error] || + membReqResCodes.default, + } + : { + message: `${toApprove ? 'Approved' : 'Rejected'} successfully`, + }), }; }); _ && result.push(_); } } else { result.push({ - requesterId: window.Store.JidToWid.userJidToUserWid(participant.participantArgs[0].participantJid)._serialized, - message: 'ServerStatusCodeError' + requesterId: window + .require('WAWebJidToWid') + .userJidToUserWid( + participant.participantArgs[0].participantJid, + )._serialized, + message: 'ServerStatusCodeError', }); } sleep && participantArgs.length > 1 && - participantArgs.indexOf(participant) !== participantArgs.length - 1 && - (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep)))); + participantArgs.indexOf(participant) !== + participantArgs.length - 1 && + (await new Promise((resolve) => + setTimeout(resolve, _getSleepTime(sleep)), + )); } return result; } catch (err) { @@ -1048,22 +1654,124 @@

Source: util/Injected/Utils.js

} }; + window.WWebJS.subscribeToUnsubscribeFromChannel = async ( + channelId, + action, + options = {}, + ) => { + const channel = await window.WWebJS.getChat(channelId, { + getAsModel: false, + }); + + if (!channel || channel.newsletterMetadata.membershipType === 'owner') + return false; + options = { + eventSurface: 3, + deleteLocalModels: options.deleteLocalModels ?? true, + }; + + try { + if (action === 'Subscribe') { + await window + .require('WAWebNewsletterSubscribeAction') + .subscribeToNewsletterAction(channel, options); + } else if (action === 'Unsubscribe') { + await window + .require('WAWebNewsletterUnsubscribeAction') + .unsubscribeFromNewsletterAction(channel, options); + } else return false; + return true; + } catch (err) { + if (err.name === 'ServerStatusCodeError') return false; + throw err; + } + }; + window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => { - const message = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; + const message = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; if (!message) return false; - const response = await window.Store.pinUnpinMsg(message, action, duration); + + if (typeof duration !== 'number') return false; + + const originalFunction = window.require( + 'WAWebPinMsgConstants', + ).getPinExpiryDuration; + window.require('WAWebPinMsgConstants').getPinExpiryDuration = () => + duration; + + const response = await window + .require('WAWebSendPinMessageAction') + .sendPinInChatMsg(message, action, duration); + + window.require('WAWebPinMsgConstants').getPinExpiryDuration = + originalFunction; + return response.messageSendResult === 'OK'; }; - - window.WWebJS.getStatusModel = status => { + + window.WWebJS.getStatusModel = (status) => { const res = status.serialize(); delete res._msgs; return res; }; window.WWebJS.getAllStatuses = () => { - const statuses = window.Store.Status.getModelsArray(); - return statuses.map(status => window.WWebJS.getStatusModel(status)); + const statuses = window + .require('WAWebCollections') + .Status.getModelsArray(); + return statuses.map((status) => window.WWebJS.getStatusModel(status)); + }; + + window.WWebJS.enforceLidAndPnRetrieval = async (userId) => { + const wid = window.require('WAWebWidFactory').createWid(userId); + const isLid = wid.server === 'lid'; + + let lid = isLid + ? wid + : window.require('WAWebApiContact').getCurrentLid(wid); + let phone = isLid + ? window.require('WAWebApiContact').getPhoneNumber(wid) + : wid; + + if (!isLid && !lid) { + const queryResult = await window + .require('WAWebQueryExistsJob') + .queryWidExists(wid); + if (!queryResult?.wid) return {}; + lid = window.require('WAWebApiContact').getCurrentLid(wid); + } + + if (isLid && !phone) { + const queryResult = await window + .require('WAWebQueryExistsJob') + .queryWidExists(wid); + if (!queryResult?.wid) return {}; + phone = window.require('WAWebApiContact').getPhoneNumber(wid); + } + + return { lid, phone }; + }; + + window.WWebJS.assertColor = (hex) => { + let color; + if (typeof hex === 'number') { + color = hex > 0 ? hex : 0xffffffff + parseInt(hex) + 1; + } else if (typeof hex === 'string') { + let number = hex.trim().replace('#', ''); + if (number.length <= 6) { + number = 'FF' + number.padStart(6, '0'); + } + color = parseInt(number, 16); + } else { + throw 'Invalid hex color'; + } + return color; }; }; @@ -1076,7 +1784,7 @@

Source: util/Injected/Utils.js

diff --git a/docs/util_InterfaceController.js.html b/docs/util_InterfaceController.js.html index c7ef9a30859..6d1b86e6d35 100644 --- a/docs/util_InterfaceController.js.html +++ b/docs/util_InterfaceController.js.html @@ -35,7 +35,6 @@

Source: util/InterfaceController.js

* Interface Controller */ class InterfaceController { - constructor(props) { this.pupPage = props.pupPage; } @@ -46,8 +45,12 @@

Source: util/InterfaceController.js

*/ async openChatWindow(chatId) { return await this.pupPage.evaluate(async (chatId) => { - const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - return await window.Store.Cmd.openChatBottom({'chat':chat}); + const chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + return await window + .require('WAWebCmd') + .Cmd.openChatBottom({ chat: chat }); }, chatId); } @@ -56,9 +59,11 @@

Source: util/InterfaceController.js

* @param {string} chatId ID of the chat drawer that will be opened */ async openChatDrawer(chatId) { - await this.pupPage.evaluate(async chatId => { - let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - await window.Store.Cmd.openDrawerMid(chat); + await this.pupPage.evaluate(async (chatId) => { + let chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + await window.require('WAWebCmd').Cmd.openDrawerMid(chat); }, chatId); } @@ -67,9 +72,11 @@

Source: util/InterfaceController.js

* @param {string} chatId ID of the chat search that will be opened */ async openChatSearch(chatId) { - await this.pupPage.evaluate(async chatId => { - let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); - await window.Store.Cmd.chatSearch(chat); + await this.pupPage.evaluate(async (chatId) => { + let chat = await window.WWebJS.getChat(chatId, { + getAsModel: false, + }); + await window.require('WAWebCmd').Cmd.chatSearch(chat); }, chatId); } @@ -79,10 +86,24 @@

Source: util/InterfaceController.js

*/ async openChatWindowAt(msgId) { await this.pupPage.evaluate(async (msgId) => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; - const chat = window.Store.Chat.get(msg.id.remote) ?? await window.Store.Chat.find(msg.id.remote); - const searchContext = await window.Store.SearchContext.getSearchContext(chat, msg.id); - await window.Store.Cmd.openChatAt({ chat: chat, msgContext: searchContext }); + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + const chat = + window.require('WAWebCollections').Chat.get(msg.id.remote) ?? + (await window + .require('WAWebCollections') + .Chat.find(msg.id.remote)); + const searchContext = await window + .require('WAWebChatMessageSearch') + .getSearchContext(chat, msg.id); + await window + .require('WAWebCmd') + .Cmd.openChatAt({ chat: chat, msgContext: searchContext }); }, msgId); } @@ -91,9 +112,15 @@

Source: util/InterfaceController.js

* @param {string} msgId ID of the message drawer that will be opened */ async openMessageDrawer(msgId) { - await this.pupPage.evaluate(async msgId => { - const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; - await window.Store.Cmd.msgInfoDrawer(msg); + await this.pupPage.evaluate(async (msgId) => { + const msg = + window.require('WAWebCollections').Msg.get(msgId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([msgId]) + )?.messages?.[0]; + await window.require('WAWebCmd').Cmd.msgInfoDrawer(msg); }, msgId); } @@ -102,7 +129,9 @@

Source: util/InterfaceController.js

*/ async closeRightDrawer() { await this.pupPage.evaluate(async () => { - await window.Store.DrawerManager.closeDrawerRight(); + await window + .require('WAWebDrawerManager') + .DrawerManager.closeDrawerRight(); }); } @@ -111,8 +140,11 @@

Source: util/InterfaceController.js

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

Source: util/InterfaceController.js

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

Source: util/InterfaceController.js

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

Source: util/InterfaceController.js

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

Source: util/InterfaceController.js

diff --git a/docs/util_Puppeteer.js.html b/docs/util_Puppeteer.js.html index 7e54d3fa4e2..b7e8bd2f109 100644 --- a/docs/util_Puppeteer.js.html +++ b/docs/util_Puppeteer.js.html @@ -51,7 +51,7 @@

Source: util/Puppeteer.js

await page.exposeFunction(name, fn); } -module.exports = {exposeFunctionIfAbsent}; +module.exports = { exposeFunctionIfAbsent }; @@ -62,7 +62,7 @@

Source: util/Puppeteer.js

diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index 8a85f6bd2f3..e3f79905bb7 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -44,15 +44,20 @@

Source: util/Util.js

*/ class Util { constructor() { - throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + throw new Error( + `The ${this.constructor.name} class may not be instantiated.`, + ); } static generateHash(length) { var result = ''; - var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var characters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; for (var i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); + result += characters.charAt( + Math.floor(Math.random() * charactersLength), + ); } return result; } @@ -80,7 +85,7 @@

Source: util/Util.js

/** * Formats a image to webp * @param {MessageMedia} media - * + * * @returns {Promise<MessageMedia>} media in webp format */ static async formatImageToWebpSticker(media, pupPage) { @@ -99,7 +104,7 @@

Source: util/Util.js

/** * Formats a video to webp * @param {MessageMedia} media - * + * * @returns {Promise<MessageMedia>} media in webp format */ static async formatVideoToWebpSticker(media) { @@ -110,13 +115,13 @@

Source: util/Util.js

const tempFile = path.join( tmpdir(), - `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp` + `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`, ); const stream = new (require('stream').Readable)(); const buffer = Buffer.from( media.data.replace(`data:${media.mimetype};base64,`, ''), - 'base64' + 'base64', ); stream.push(buffer); stream.push(null); @@ -131,7 +136,7 @@

Source: util/Util.js

'libwebp', '-vf', // eslint-disable-next-line no-useless-escape - 'scale=\'iw*min(300/iw\,300/ih)\':\'ih*min(300/iw\,300/ih)\',format=rgba,pad=300:300:\'(300-iw)/2\':\'(300-ih)/2\':\'#00000000\',setsar=1,fps=10', + "scale='iw*min(300/iw\,300/ih)':'ih*min(300/iw\,300/ih)',format=rgba,pad=300:300:'(300-iw)/2':'(300-ih)/2':'#00000000',setsar=1,fps=10", '-loop', '0', '-ss', @@ -163,8 +168,8 @@

Source: util/Util.js

/** * Sticker metadata. * @typedef {Object} StickerMetadata - * @property {string} [name] - * @property {string} [author] + * @property {string} [name] + * @property {string} [author] * @property {string[]} [categories] */ @@ -172,7 +177,7 @@

Source: util/Util.js

* Formats a media to webp * @param {MessageMedia} media * @param {StickerMetadata} metadata - * + * * @returns {Promise<MessageMedia>} media in webp format */ static async formatToWebpSticker(media, metadata, pupPage) { @@ -182,8 +187,7 @@

Source: util/Util.js

webpMedia = await this.formatImageToWebpSticker(media, pupPage); else if (media.mimetype.includes('video')) webpMedia = await this.formatVideoToWebpSticker(media); - else - throw new Error('Invalid media format'); + else throw new Error('Invalid media format'); if (metadata.name || metadata.author) { const img = new webp.Image(); @@ -192,8 +196,17 @@

Source: util/Util.js

const packname = metadata.name; const author = metadata.author; const categories = metadata.categories || ['']; - const json = { 'sticker-pack-id': stickerPackId, 'sticker-pack-name': packname, 'sticker-pack-publisher': author, 'emojis': categories }; - let exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]); + const json = { + 'sticker-pack-id': stickerPackId, + 'sticker-pack-name': packname, + 'sticker-pack-publisher': author, + emojis: categories, + }; + let exifAttr = Buffer.from([ + 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, + 0x00, 0x00, + ]); let jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8'); let exif = Buffer.concat([exifAttr, jsonBuffer]); exif.writeUIntLE(jsonBuffer.length, 14, 4); @@ -225,7 +238,7 @@

Source: util/Util.js

diff --git a/docs/webCache_LocalWebCache.js.html b/docs/webCache_LocalWebCache.js.html index afd8afbee76..e59e7c9ed84 100644 --- a/docs/webCache_LocalWebCache.js.html +++ b/docs/webCache_LocalWebCache.js.html @@ -37,7 +37,7 @@

Source: webCache/LocalWebCache.js

/** * LocalWebCache - Fetches a WhatsApp Web version from a local file store * @param {object} options - options - * @param {string} options.path - Path to the directory where cached versions are saved, default is: "./.wwebjs_cache/" + * @param {string} options.path - Path to the directory where cached versions are saved, default is: "./.wwebjs_cache/" * @param {boolean} options.strict - If true, will throw an error if the requested version can't be fetched. If false, will resolve to the latest version. */ class LocalWebCache extends WebCache { @@ -50,12 +50,14 @@

Source: webCache/LocalWebCache.js

async resolve(version) { const filePath = path.join(this.path, `${version}.html`); - + try { return fs.readFileSync(filePath, 'utf-8'); - } - catch (err) { - if (this.strict) throw new VersionResolveError(`Couldn't load version ${version} from the cache`); + } catch (err) { + if (this.strict) + throw new VersionResolveError( + `Couldn't load version ${version} from the cache`, + ); return null; } } @@ -79,7 +81,7 @@

Source: webCache/LocalWebCache.js

diff --git a/docs/webCache_RemoteWebCache.js.html b/docs/webCache_RemoteWebCache.js.html index 96044db0263..3154b665a38 100644 --- a/docs/webCache_RemoteWebCache.js.html +++ b/docs/webCache_RemoteWebCache.js.html @@ -42,7 +42,10 @@

Source: webCache/RemoteWebCache.js

constructor(options = {}) { super(); - if (!options.remotePath) throw new Error('webVersionCache.remotePath is required when using the remote cache'); + if (!options.remotePath) + throw new Error( + 'webVersionCache.remotePath is required when using the remote cache', + ); this.remotePath = options.remotePath; this.strict = options.strict || false; } @@ -59,8 +62,11 @@

Source: webCache/RemoteWebCache.js

console.error(`Error fetching version ${version} from remote`, err); } - if (this.strict) throw new VersionResolveError(`Couldn't load version ${version} from the archive`); - return null; + if (this.strict) + throw new VersionResolveError( + `Couldn't load version ${version} from the archive`, + ); + return null; } async persist() { @@ -68,7 +74,8 @@

Source: webCache/RemoteWebCache.js

} } -module.exports = RemoteWebCache; +module.exports = RemoteWebCache; + @@ -78,7 +85,7 @@

Source: webCache/RemoteWebCache.js

diff --git a/docs/webCache_WebCache.js.html b/docs/webCache_WebCache.js.html index 7fc1d2275e5..e8120b740da 100644 --- a/docs/webCache_WebCache.js.html +++ b/docs/webCache_WebCache.js.html @@ -33,16 +33,19 @@

Source: webCache/WebCache.js

* Default implementation of a web version cache that does nothing. */ class WebCache { - async resolve() { return null; } - async persist() { } + async resolve() { + return null; + } + async persist() {} } -class VersionResolveError extends Error { } +class VersionResolveError extends Error {} module.exports = { WebCache, - VersionResolveError -}; + VersionResolveError, +}; + @@ -52,7 +55,7 @@

Source: webCache/WebCache.js

diff --git a/index.d.ts b/index.d.ts index 9fc11bb5cc9..13051ee264c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -210,6 +210,12 @@ declare namespace WAWebJS { options?: MessageSendOptions, ): Promise; + /** Send a reaction to a specific messageId */ + sendReaction( + messageId: string, + reaction: string, + ): Promise; + /** Sends a channel admin invitation to a user, allowing them to become an admin of the channel */ sendChannelAdminInvite( chatId: string, diff --git a/src/Client.js b/src/Client.js index 145e1104665..15727c8efbf 100644 --- a/src/Client.js +++ b/src/Client.js @@ -1540,6 +1540,33 @@ class Client extends EventEmitter { return sentMsg ? new Message(this, sentMsg) : undefined; } + /** + * Send an emoji reaction to a specific message + * @param {string} messageId - Id of the message to add the reaction. + * @param {string} reaction - Emoji to react with. Send an empty string to remove the reaction. + * @return {Promise} + */ + async sendReaction(messageId, reaction) { + await this.pupPage.evaluate( + async (messageId, reaction) => { + if (!messageId) return null; + const msg = + window.require('WAWebCollections').Msg.get(messageId) || + ( + await window + .require('WAWebCollections') + .Msg.getMessagesById([messageId]) + )?.messages?.[0]; + if (!msg) return null; + await window + .require('WAWebSendReactionMsgAction') + .sendReactionToMsg(msg, reaction); + }, + messageId, + reaction, + ); + } + /** * @typedef {Object} SendChannelAdminInviteOptions * @property {?string} comment The comment to be added to an invitation diff --git a/src/structures/Message.js b/src/structures/Message.js index 63ae36556e7..a7fdd82046e 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -477,24 +477,7 @@ class Message extends Base { * @return {Promise} */ async react(reaction) { - await this.client.pupPage.evaluate( - async (messageId, reaction) => { - if (!messageId) return null; - const msg = - window.require('WAWebCollections').Msg.get(messageId) || - ( - await window - .require('WAWebCollections') - .Msg.getMessagesById([messageId]) - )?.messages?.[0]; - if (!msg) return null; - await window - .require('WAWebSendReactionMsgAction') - .sendReactionToMsg(msg, reaction); - }, - this.id._serialized, - reaction, - ); + return this.client.sendReaction(this.id._serialized, reaction); } /**