From c1ae300cbb6d8aab36152f3acfb780aa4a0dbaed Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 14 Mar 2024 11:15:52 +0200 Subject: [PATCH 01/47] fix(webpack-exodus): Added the new patch --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa338e7b28..c28702d918 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://wwebjs.dev/", "dependencies": { - "@pedroslopez/moduleraid": "^5.0.2", + "@pedroslopez/moduleraid": "github:wwebjs/moduleRaid", "fluent-ffmpeg": "2.1.2", "mime": "^3.0.0", "node-fetch": "^2.6.5", From db72385433e07bcf091224f0a8db248072a76d7a Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 3 Apr 2024 03:52:06 +0300 Subject: [PATCH 02/47] fix(webpack-exodus): Fixed v2.3000.x and v2.24 --- index.js | 1 - package.json | 2 +- src/Client.js | 58 +++---- src/authStrategies/LegacySessionAuth.js | 72 --------- src/structures/GroupChat.js | 4 +- src/util/Injected/LegacyStore.js | 149 ++++++++++++++++++ src/util/Injected/Store.js | 119 +++++++++++++++ src/util/{Injected.js => Injected/Utils.js} | 160 +------------------- src/webCache/LocalWebCache.js | 6 +- 9 files changed, 307 insertions(+), 264 deletions(-) delete mode 100644 src/authStrategies/LegacySessionAuth.js create mode 100644 src/util/Injected/LegacyStore.js create mode 100644 src/util/Injected/Store.js rename src/util/{Injected.js => Injected/Utils.js} (79%) diff --git a/index.js b/index.js index 9b7bf85bac..dd991f585c 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,6 @@ module.exports = { NoAuth: require('./src/authStrategies/NoAuth'), LocalAuth: require('./src/authStrategies/LocalAuth'), RemoteAuth: require('./src/authStrategies/RemoteAuth'), - LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'), ...Constants }; diff --git a/package.json b/package.json index c28702d918..aa338e7b28 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://wwebjs.dev/", "dependencies": { - "@pedroslopez/moduleraid": "github:wwebjs/moduleRaid", + "@pedroslopez/moduleraid": "^5.0.2", "fluent-ffmpeg": "2.1.2", "mime": "^3.0.0", "node-fetch": "^2.6.5", diff --git a/src/Client.js b/src/Client.js index c0c1db0640..af21f0624b 100644 --- a/src/Client.js +++ b/src/Client.js @@ -7,12 +7,13 @@ const moduleRaid = require('@pedroslopez/moduleraid/moduleraid'); const Util = require('./util/Util'); const InterfaceController = require('./util/InterfaceController'); const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants'); -const { ExposeStore, LoadUtils } = require('./util/Injected'); +const { ExposeStore } = require('./util/Injected/Store'); +const { LoadUtils } = require('./util/Injected/Utils'); +const { ExposeLegacyStore } = require('./util/Injected/LegacyStore'); const ChatFactory = require('./factories/ChatFactory'); const ContactFactory = require('./factories/ContactFactory'); const WebCacheFactory = require('./webCache/WebCacheFactory'); const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures'); -const LegacySessionAuth = require('./authStrategies/LegacySessionAuth'); const NoAuth = require('./authStrategies/NoAuth'); /** @@ -62,20 +63,7 @@ class Client extends EventEmitter { this.options = Util.mergeDefault(DefaultOptions, options); if(!this.options.authStrategy) { - if(Object.prototype.hasOwnProperty.call(this.options, 'session')) { - process.emitWarning( - 'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' + - 'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).', - 'DeprecationWarning' - ); - - this.authStrategy = new LegacySessionAuth({ - session: this.options.session, - restartOnAuthFail: this.options.restartOnAuthFail - }); - } else { - this.authStrategy = new NoAuth(); - } + this.authStrategy = new NoAuth(); } else { this.authStrategy = this.options.authStrategy; } @@ -85,6 +73,8 @@ class Client extends EventEmitter { this.pupBrowser = null; this.pupPage = null; + this.currentIndexHtml = null; + Util.setFfmpegPath(this.options.ffmpegPath); } @@ -125,7 +115,8 @@ class Client extends EventEmitter { await this.authStrategy.afterBrowserInitialized(); await this.initWebVersionCache(); - // ocVesion (isOfficialClient patch) + // ocVersion (isOfficialClient patch) + // remove on after 2.3000.x await page.evaluateOnNewDocument(() => { const originalError = Error; //eslint-disable-next-line no-global-assign @@ -336,7 +327,23 @@ class Client extends EventEmitter { }; }); - await page.evaluate(ExposeStore, moduleRaid.toString()); + const version = await this.getWWebVersion(); + const isCometOrAbove = parseInt(version.split(".")?.[1]) >= 3000; + + 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 page.evaluate(ExposeStore); + } else { + await page.evaluate(ExposeLegacyStore, moduleRaid.toString()); + } + + const authEventPayload = await this.authStrategy.getAuthEventPayload(); /** @@ -768,7 +775,8 @@ class Client extends EventEmitter { } else { this.pupPage.on('response', async (res) => { if(res.ok() && res.url() === WhatsWebURL) { - await webCache.persist(await res.text()); + const indexHtml = await res.text(); + this.currentIndexHtml = indexHtml; } }); } @@ -1087,14 +1095,8 @@ class Client extends EventEmitter { async setDisplayName(displayName) { const couldSet = await this.pupPage.evaluate(async displayName => { if(!window.Store.Conn.canSetMyPushname()) return false; - - if(window.Store.MDBackend) { - await window.Store.Settings.setPushname(displayName); - return true; - } else { - const res = await window.Store.Wap.setPushname(displayName); - return !res.status || res.status === 200; - } + await window.Store.Settings.setPushname(displayName); + return true; }, displayName); return couldSet; @@ -1416,7 +1418,7 @@ class Client extends EventEmitter { const statusCode = participant.error ?? 200; if (autoSendInviteV4 && statusCode === 403) { - window.Store.ContactCollection.gadd(participant.wid, { silent: true }); + window.Store.Contact.gadd(participant.wid, { silent: true }); const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage( await window.Store.Chat.find(participant.wid), createGroupResult.wid._serialized, diff --git a/src/authStrategies/LegacySessionAuth.js b/src/authStrategies/LegacySessionAuth.js deleted file mode 100644 index dd09f7b5e6..0000000000 --- a/src/authStrategies/LegacySessionAuth.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -const BaseAuthStrategy = require('./BaseAuthStrategy'); - -/** - * Legacy session auth strategy - * Not compatible with multi-device accounts. - * @param {object} options - options - * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails - * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session - * @param {string} options.session.WABrowserId - * @param {string} options.session.WASecretBundle - * @param {string} options.session.WAToken1 - * @param {string} options.session.WAToken2 - */ -class LegacySessionAuth extends BaseAuthStrategy { - constructor({ session, restartOnAuthFail }={}) { - super(); - this.session = session; - this.restartOnAuthFail = restartOnAuthFail; - } - - async afterBrowserInitialized() { - if(this.session) { - await this.client.pupPage.evaluateOnNewDocument(session => { - if (document.referrer === 'https://whatsapp.com/') { - localStorage.clear(); - localStorage.setItem('WABrowserId', session.WABrowserId); - localStorage.setItem('WASecretBundle', session.WASecretBundle); - localStorage.setItem('WAToken1', session.WAToken1); - localStorage.setItem('WAToken2', session.WAToken2); - } - - localStorage.setItem('remember-me', 'true'); - }, this.session); - } - } - - async onAuthenticationNeeded() { - if(this.session) { - this.session = null; - return { - failed: true, - restart: this.restartOnAuthFail, - failureEventPayload: 'Unable to log in. Are the session details valid?' - }; - } - - return { failed: false }; - } - - async getAuthEventPayload() { - const isMD = await this.client.pupPage.evaluate(() => { - return window.Store.MDBackend; - }); - - if(isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.'); - - const localStorage = JSON.parse(await this.client.pupPage.evaluate(() => { - return JSON.stringify(window.localStorage); - })); - - return { - WABrowserId: localStorage.WABrowserId, - WASecretBundle: localStorage.WASecretBundle, - WAToken1: localStorage.WAToken1, - WAToken2: localStorage.WAToken2 - }; - } -} - -module.exports = LegacySessionAuth; diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index d974f9d296..934639de40 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -98,7 +98,7 @@ class GroupChat extends Chat { 419: 'The participant can\'t be added because the group is full' }; - await window.Store.GroupMetadata.queryAndUpdate(groupWid); + await window.Store.GroupQueryAndUpdate(groupWid); const groupMetadata = group.groupMetadata; const groupParticipants = groupMetadata?.participants; @@ -152,7 +152,7 @@ class GroupChat extends Chat { if (autoSendInviteV4 && rpcResultCode === 403) { let userChat, isInviteV4Sent = false; - window.Store.ContactCollection.gadd(pWid, { silent: true }); + window.Store.Contact.gadd(pWid, { silent: true }); if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' && (userChat = await window.Store.Chat.find(pWid))) { diff --git a/src/util/Injected/LegacyStore.js b/src/util/Injected/LegacyStore.js new file mode 100644 index 0000000000..57c504d45c --- /dev/null +++ b/src/util/Injected/LegacyStore.js @@ -0,0 +1,149 @@ +'use strict'; + +//TODO: To be removed y version 2.3000.x hard release + +// Exposes the internal Store to the WhatsApp Web client +exports.ExposeStore = (moduleRaidStr) => { + eval('var moduleRaid = ' + moduleRaidStr); + // eslint-disable-next-line no-undef + window.mR = moduleRaid(); + window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); + window.Store.AppState = window.mR.findModule('Socket')[0].Socket; + window.Store.Conn = window.mR.findModule('Conn')[0].Conn; + window.Store.BlockContact = window.mR.findModule('blockContact')[0]; + window.Store.Call = window.mR.findModule((module) => module.default && module.default.Call)[0].default.Call; + window.Store.Cmd = window.mR.findModule('Cmd')[0].Cmd; + window.Store.CryptoLib = window.mR.findModule('decryptE2EMedia')[0]; + window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager; + window.Store.GroupMetadata = window.mR.findModule('GroupMetadata')[0].default.GroupMetadata; + window.Store.GroupQueryAndUpdate = window.mR.findModule('queryAndUpdateGroupMetadataById')[0].queryAndUpdateGroupMetadataById; + window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; + window.Store.MediaPrep = window.mR.findModule('prepRawMedia')[0]; + window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0]; + window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0]; + window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0]; + window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0]; + window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default; + window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default; + window.Store.QueryProduct = window.mR.findModule('queryProduct')[0]; + window.Store.QueryOrder = window.mR.findModule('queryOrder')[0]; + window.Store.SendClear = window.mR.findModule('sendClear')[0]; + window.Store.SendDelete = window.mR.findModule('sendDelete')[0]; + window.Store.SendMessage = window.mR.findModule('addAndSendMsgToChat')[0]; + window.Store.EditMessage = window.mR.findModule('addAndSendMessageEdit')[0]; + window.Store.SendSeen = window.mR.findModule('sendSeen')[0]; + window.Store.User = window.mR.findModule('getMaybeMeUser')[0]; + window.Store.ContactMethods = window.mR.findModule('getUserid')[0]; + window.Store.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; + window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default; + window.Store.Validators = window.mR.findModule('findLinks')[0]; + window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; + window.Store.WidFactory = window.mR.findModule('createWid')[0]; + window.Store.ProfilePic = window.mR.findModule('profilePicResync')[0]; + window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0]; + window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0]; + window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups; + window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0]; + window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0]; + window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg; + window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0]; + window.Store.EphemeralFields = window.mR.findModule('getEphemeralFields')[0]; + window.Store.MsgActionChecks = window.mR.findModule('canSenderRevokeMsg')[0]; + window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0]; + window.Store.LinkPreview = window.mR.findModule('getLinkPreview')[0]; + window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0]; + window.Store.SocketWap = window.mR.findModule('wap')[0]; + window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext; + window.Store.DrawerManager = window.mR.findModule('DrawerManager')[0].DrawerManager; + window.Store.LidUtils = window.mR.findModule('getCurrentLid')[0]; + window.Store.WidToJid = window.mR.findModule('widToUserJid')[0]; + window.Store.JidToWid = window.mR.findModule('userJidToUserWid')[0]; + window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo; + window.Store.pinUnpinMsg = window.mR.findModule('sendPinInChatMsg')[0].sendPinInChatMsg; + + /* eslint-disable no-undef, no-cond-assign */ + window.Store.QueryExist = ((m = window.mR.findModule('queryExists')[0]) ? m.queryExists : window.mR.findModule('queryExist')[0].queryWidExists); + window.Store.ReplyUtils = (m = window.mR.findModule('canReplyMsg')).length > 0 && m[0]; + /* eslint-enable no-undef, no-cond-assign */ + + window.Store.Settings = { + ...window.mR.findModule('ChatlistPanelState')[0], + setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname + }; + window.Store.StickerTools = { + ...window.mR.findModule('toWebpSticker')[0], + ...window.mR.findModule('addWebpMetadata')[0] + }; + window.Store.GroupUtils = { + ...window.mR.findModule('createGroup')[0], + ...window.mR.findModule('setGroupDescription')[0], + ...window.mR.findModule('sendExitGroup')[0], + ...window.mR.findModule('sendSetPicture')[0] + }; + window.Store.GroupParticipants = { + ...window.mR.findModule('promoteParticipants')[0], + ...window.mR.findModule('sendAddParticipantsRPC')[0] + }; + window.Store.GroupInvite = { + ...window.mR.findModule('resetGroupInviteCode')[0], + ...window.mR.findModule('queryGroupInvite')[0] + }; + window.Store.GroupInviteV4 = { + ...window.mR.findModule('queryGroupInviteV4')[0], + ...window.mR.findModule('sendGroupInviteMessage')[0] + }; + window.Store.MembershipRequestUtils = { + ...window.mR.findModule('getMembershipApprovalRequests')[0], + ...window.mR.findModule('sendMembershipRequestsActionRPC')[0] + }; + + if (!window.Store.Chat._find) { + window.Store.Chat._find = e => { + const target = window.Store.Chat.get(e); + return target ? Promise.resolve(target) : Promise.resolve({ + id: e + }); + }; + } + + // eslint-disable-next-line no-undef + if ((m = window.mR.findModule('ChatCollection')[0]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; + + const _isMDBackend = window.mR.findModule('isMDBackend'); + if(_isMDBackend && _isMDBackend[0] && _isMDBackend[0].isMDBackend) { + window.Store.MDBackend = _isMDBackend[0].isMDBackend(); + } else { + window.Store.MDBackend = true; + } + + const _features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0]; + if(_features) { + window.Store.Features = _features.LegacyPhoneFeatures; + } + + /** + * Target options object description + * @typedef {Object} TargetOptions + * @property {string|number} module The name or a key of the target module to search + * @property {number} index The index value of the target module + * @property {string} function The function name to get from a module + */ + + /** + * Function to modify functions + * @param {TargetOptions} target Options specifying the target function to search for modifying + * @param {Function} callback Modified function + */ + window.injectToFunction = (target, callback) => { + const module = typeof target.module === 'string' + ? window.mR.findModule(target.module) + : window.mR.modules[target.module]; + const originalFunction = module[target.index][target.function]; + const modifiedFunction = (...args) => callback(originalFunction, ...args); + module[target.index][target.function] = modifiedFunction; + }; + + window.injectToFunction({ module: 'mediaTypeFromProtobuf', index: 0, function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); + + window.injectToFunction({ module: 'typeAttributeFromProtobuf', index: 0, function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); +}; \ No newline at end of file diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js new file mode 100644 index 0000000000..a34d6a1869 --- /dev/null +++ b/src/util/Injected/Store.js @@ -0,0 +1,119 @@ +'use strict'; + +exports.ExposeStore = () => { + if (!require) { + require = window.require; + } + + window.Store = Object.assign({}, require('WAWebCollections')); + window.Store.AppState = require('WAWebSocketModel').Socket; + window.Store.BlockContact = require('WAWebBlockContactAction'); + window.Store.Conn = require('WAWebConnModel').Conn; + window.Store.Cmd = require('WAWebCmd').Cmd; + window.Store.DownloadManager = require('WAWebDownloadManager').downloadManager; + window.Store.GroupQueryAndUpdate = require('WAWebGroupQueryJob').queryAndUpdateGroupMetadataById; + window.Store.MediaPrep = require('WAWebPrepRawMedia'); + window.Store.MediaObject = require('WAWebMediaStorage').getOrCreateMediaObject; + window.Store.MediaTypes = require('WAWebMmsMediaTypes'); + window.Store.MediaUpload = require('WAWebMediaMmsV4Upload'); + window.Store.MsgKey = require('WAWebMsgKey'); + window.Store.NumberInfo = require('WAPhoneUtils'); + window.Store.OpaqueData = require('WAWebMediaOpaqueData'); + window.Store.QueryProduct = require('WAWebBizProductCatalogBridge'); + window.Store.QueryOrder = require('WAWebBizQueryOrderJob'); + window.Store.SendClear = require('WAWebChatClearBridge'); + window.Store.SendDelete = require('WAWebDeleteChatAction'); + window.Store.SendMessage = require('WAWebSendMsgChatAction'); + window.Store.EditMessage = require('WAWebSendMessageEditAction'); + window.Store.SendSeen = require('WAWebUpdateUnreadChatAction'); + window.Store.User = require('WAWebUserPrefsMeUser'); + window.Store.ContactMethods = require('WAWebContactGetters'); + window.Store.UploadUtils = require('WAWebUploadManager'); + window.Store.UserConstructor = require('WAWebWid'); + window.Store.Validators = require('WALinkify'); + window.Store.VCard = require('WAWebFrontendVcardUtils'); + window.Store.WidFactory = require('WAWebWidFactory'); + window.Store.ProfilePic = require('WAWebContactProfilePicThumbBridge'); + window.Store.PresenceUtils = require('WAWebPresenceChatAction'); + window.Store.ChatState = require('WAWebChatStateBridge'); + window.Store.findCommonGroups = require('WAWebFindCommonGroupsContactAction').findCommonGroups; + window.Store.StatusUtils = require('WAWebContactStatusBridge'); + window.Store.ConversationMsgs = require('WAWebChatLoadMessages'); + window.Store.sendReactionToMsg = require('WAWebSendReactionMsgAction').sendReactionToMsg; + window.Store.createOrUpdateReactionsModule = require('WAWebDBCreateOrUpdateReactions'); + window.Store.EphemeralFields = require('WAWebGetEphemeralFieldsMsgActionsUtils'); + window.Store.MsgActionChecks = require('WAWebMsgActionCapability'); + window.Store.QuotedMsg = require('WAWebQuotedMsgModelUtils'); + window.Store.LinkPreview = require('WAWebMsgGetters'); + window.Store.Socket = require('WADeprecatedSendIq'); + window.Store.SocketWap = require('WAWap'); + window.Store.SearchContext = require('WAWebChatMessageSearch').getSearchContext; + window.Store.DrawerManager = require('WAWebDrawerManager').DrawerManager; + window.Store.LidUtils = require('WAWebApiContact'); + window.Store.WidToJid = require('WAWebWidToJid'); + window.Store.JidToWid = require('WAWebJidToWid'); + window.Store.getMsgInfo = require('WAWebApiMessageInfoStore').queryMsgInfo; + window.Store.pinUnpinMsg = require('WAWebSendPinMessageAction').sendPinInChatMsg; + window.Store.QueryExist = require('WAWebQueryExistsJob').queryWidExists; + window.Store.ReplyUtils = require('WAWebMsgReply'); + window.Store.Settings = require('WAWebUserPrefsGeneral'); + + window.Store.StickerTools = { + ...require('WAWebImageUtils'), + ...require('WAWebAddWebpMetadata') + }; + window.Store.GroupUtils = { + ...require('WAWebGroupCreateJob'), + ...require('WAWebGroupModifyInfoJob'), + ...require('WAWebExitGroupAction'), + ...require('WAWebContactProfilePicThumbBridge') + }; + window.Store.GroupParticipants = { + ...require('promoteParticipants'), + ...require('sendAddParticipantsRPC') + }; + window.Store.GroupInvite = { + ...require('resetGroupInviteCode'), + ...require('queryGroupInvite') + }; + window.Store.GroupInviteV4 = { + ...require('queryGroupInviteV4'), + ...require('sendGroupInviteMessage') + }; + window.Store.MembershipRequestUtils = { + ...require('getMembershipApprovalRequests'), + ...require('sendMembershipRequestsActionRPC') + }; + + if (!window.Store.Chat._find || !window.Store.Chat.findImpl) { + window.Store.Chat._find = e => { + const target = window.Store.Chat.get(e); + return target ? Promise.resolve(target) : Promise.resolve({ + id: e + }); + }; + window.Store.Chat.findImpl = window.Store.Chat._find + } + + /** + * Target options object description + * @typedef {Object} TargetOptions + * @property {string|number} module The target module + * @property {string} function The function name to get from a module + */ + /** + * Function to modify functions + * @param {TargetOptions} target Options specifying the target function to search for modifying + * @param {Function} callback Modified function + */ + window.injectToFunction = (target, callback) => { + const module = require(target.module); + const originalFunction = module[target.function]; + const modifiedFunction = (...args) => callback(originalFunction, ...args); + module[target.function] = modifiedFunction; + }; + + window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); + + window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); +} \ No newline at end of file diff --git a/src/util/Injected.js b/src/util/Injected/Utils.js similarity index 79% rename from src/util/Injected.js rename to src/util/Injected/Utils.js index 3e85a91ef9..9f9440641a 100644 --- a/src/util/Injected.js +++ b/src/util/Injected/Utils.js @@ -1,153 +1,5 @@ 'use strict'; -// Exposes the internal Store to the WhatsApp Web client -exports.ExposeStore = (moduleRaidStr) => { - eval('var moduleRaid = ' + moduleRaidStr); - // eslint-disable-next-line no-undef - window.mR = moduleRaid(); - window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); - window.Store.AppState = window.mR.findModule('Socket')[0].Socket; - window.Store.Conn = window.mR.findModule('Conn')[0].Conn; - window.Store.BlockContact = window.mR.findModule('blockContact')[0]; - window.Store.Call = window.mR.findModule((module) => module.default && module.default.Call)[0].default.Call; - window.Store.Cmd = window.mR.findModule('Cmd')[0].Cmd; - window.Store.CryptoLib = window.mR.findModule('decryptE2EMedia')[0]; - window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager; - window.Store.GroupMetadata = window.mR.findModule('GroupMetadata')[0].default.GroupMetadata; - window.Store.GroupMetadata.queryAndUpdate = window.mR.findModule('queryAndUpdateGroupMetadataById')[0].queryAndUpdateGroupMetadataById; - window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; - window.Store.ContactCollection = window.mR.findModule('ContactCollection')[0].ContactCollection; - window.Store.MediaPrep = window.mR.findModule('prepRawMedia')[0]; - window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0]; - window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0]; - window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0]; - window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0]; - window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default; - window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default; - window.Store.QueryProduct = window.mR.findModule('queryProduct')[0]; - window.Store.QueryOrder = window.mR.findModule('queryOrder')[0]; - window.Store.SendClear = window.mR.findModule('sendClear')[0]; - window.Store.SendDelete = window.mR.findModule('sendDelete')[0]; - window.Store.SendMessage = window.mR.findModule('addAndSendMsgToChat')[0]; - window.Store.EditMessage = window.mR.findModule('addAndSendMessageEdit')[0]; - window.Store.SendSeen = window.mR.findModule('sendSeen')[0]; - window.Store.User = window.mR.findModule('getMaybeMeUser')[0]; - window.Store.ContactMethods = window.mR.findModule('getUserid')[0]; - window.Store.BusinessProfileCollection = window.mR.findModule('BusinessProfileCollection')[0].BusinessProfileCollection; - window.Store.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; - window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default; - window.Store.Validators = window.mR.findModule('findLinks')[0]; - window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; - window.Store.WidFactory = window.mR.findModule('createWid')[0]; - window.Store.ProfilePic = window.mR.findModule('profilePicResync')[0]; - window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0]; - window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0]; - window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups; - window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0]; - window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0]; - window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg; - window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0]; - window.Store.EphemeralFields = window.mR.findModule('getEphemeralFields')[0]; - window.Store.MsgActionChecks = window.mR.findModule('canSenderRevokeMsg')[0]; - window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0]; - window.Store.LinkPreview = window.mR.findModule('getLinkPreview')[0]; - window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0]; - window.Store.SocketWap = window.mR.findModule('wap')[0]; - window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext; - window.Store.DrawerManager = window.mR.findModule('DrawerManager')[0].DrawerManager; - window.Store.LidUtils = window.mR.findModule('getCurrentLid')[0]; - window.Store.WidToJid = window.mR.findModule('widToUserJid')[0]; - window.Store.JidToWid = window.mR.findModule('userJidToUserWid')[0]; - window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo; - window.Store.pinUnpinMsg = window.mR.findModule('sendPinInChatMsg')[0].sendPinInChatMsg; - - /* eslint-disable no-undef, no-cond-assign */ - window.Store.QueryExist = ((m = window.mR.findModule('queryExists')[0]) ? m.queryExists : window.mR.findModule('queryExist')[0].queryWidExists); - window.Store.ReplyUtils = (m = window.mR.findModule('canReplyMsg')).length > 0 && m[0]; - /* eslint-enable no-undef, no-cond-assign */ - - window.Store.Settings = { - ...window.mR.findModule('ChatlistPanelState')[0], - setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname - }; - window.Store.StickerTools = { - ...window.mR.findModule('toWebpSticker')[0], - ...window.mR.findModule('addWebpMetadata')[0] - }; - window.Store.GroupUtils = { - ...window.mR.findModule('createGroup')[0], - ...window.mR.findModule('setGroupDescription')[0], - ...window.mR.findModule('sendExitGroup')[0], - ...window.mR.findModule('sendSetPicture')[0] - }; - window.Store.GroupParticipants = { - ...window.mR.findModule('promoteParticipants')[0], - ...window.mR.findModule('sendAddParticipantsRPC')[0] - }; - window.Store.GroupInvite = { - ...window.mR.findModule('resetGroupInviteCode')[0], - ...window.mR.findModule('queryGroupInvite')[0] - }; - window.Store.GroupInviteV4 = { - ...window.mR.findModule('queryGroupInviteV4')[0], - ...window.mR.findModule('sendGroupInviteMessage')[0] - }; - window.Store.MembershipRequestUtils = { - ...window.mR.findModule('getMembershipApprovalRequests')[0], - ...window.mR.findModule('sendMembershipRequestsActionRPC')[0] - }; - - if (!window.Store.Chat._find) { - window.Store.Chat._find = e => { - const target = window.Store.Chat.get(e); - return target ? Promise.resolve(target) : Promise.resolve({ - id: e - }); - }; - } - - // eslint-disable-next-line no-undef - if ((m = window.mR.findModule('ChatCollection')[0]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; - - const _isMDBackend = window.mR.findModule('isMDBackend'); - if(_isMDBackend && _isMDBackend[0] && _isMDBackend[0].isMDBackend) { - window.Store.MDBackend = _isMDBackend[0].isMDBackend(); - } else { - window.Store.MDBackend = true; - } - - const _features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0]; - if(_features) { - window.Store.Features = _features.LegacyPhoneFeatures; - } - - /** - * Target options object description - * @typedef {Object} TargetOptions - * @property {string|number} module The name or a key of the target module to search - * @property {number} index The index value of the target module - * @property {string} function The function name to get from a module - */ - - /** - * Function to modify functions - * @param {TargetOptions} target Options specifying the target function to search for modifying - * @param {Function} callback Modified function - */ - window.injectToFunction = (target, callback) => { - const module = typeof target.module === 'string' - ? window.mR.findModule(target.module) - : window.mR.modules[target.module]; - const originalFunction = module[target.index][target.function]; - const modifiedFunction = (...args) => callback(originalFunction, ...args); - module[target.index][target.function] = modifiedFunction; - }; - - window.injectToFunction({ module: 'mediaTypeFromProtobuf', index: 0, function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); - - window.injectToFunction({ module: 'typeAttributeFromProtobuf', index: 0, function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); -}; - exports.LoadUtils = () => { window.WWebJS = {}; @@ -333,14 +185,13 @@ exports.LoadUtils = () => { } const meUser = window.Store.User.getMaybeMeUser(); - const isMD = window.Store.MDBackend; const newId = await window.Store.MsgKey.newId(); const newMsgId = new window.Store.MsgKey({ from: meUser, to: chat.id, id: newId, - participant: isMD && chat.id.isGroup() ? meUser : undefined, + participant: chat.id.isGroup() ? meUser : undefined, selfDir: 'out', }); @@ -657,7 +508,7 @@ exports.LoadUtils = () => { window.WWebJS.getContact = async contactId => { const wid = window.Store.WidFactory.createWid(contactId); const contact = await window.Store.Contact.find(wid); - const bizProfile = await window.Store.BusinessProfileCollection.fetchBizProfile(wid); + const bizProfile = await window.Store.BusinessProfile.fetchBizProfile(wid); bizProfile.profileOptions && (contact.businessProfile = bizProfile); return window.WWebJS.getContactModel(contact); }; @@ -778,9 +629,8 @@ exports.LoadUtils = () => { }; window.WWebJS.sendChatstate = async (state, chatId) => { - if (window.Store.MDBackend) { - chatId = window.Store.WidFactory.createWid(chatId); - } + chatId = window.Store.WidFactory.createWid(chatId); + switch (state) { case 'typing': await window.Store.ChatState.sendChatStateComposing(chatId); @@ -1028,7 +878,7 @@ exports.LoadUtils = () => { let response; let result = []; - await window.Store.GroupMetadata.queryAndUpdate(groupWid); + await window.Store.GroupQueryAndUpdate(groupWid); if (!requesterIds?.length) { membershipRequests = group.groupMetadata.membershipApprovalRequests._models.map(({ id }) => id); diff --git a/src/webCache/LocalWebCache.js b/src/webCache/LocalWebCache.js index a377f559b5..5be049e54c 100644 --- a/src/webCache/LocalWebCache.js +++ b/src/webCache/LocalWebCache.js @@ -29,11 +29,7 @@ class LocalWebCache extends WebCache { } } - async persist(indexHtml) { - // extract version from index (e.g. manifest-2.2206.9.json -> 2.2206.9) - const version = indexHtml.match(/manifest-([\d\\.]+)\.json/)[1]; - if(!version) return; - + async persist(indexHtml, version) { const filePath = path.join(this.path, `${version}.html`); fs.mkdirSync(this.path, { recursive: true }); fs.writeFileSync(filePath, indexHtml); From 24196dbc0b235ba543c53a80d0f79c420de10e37 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 3 Apr 2024 04:03:28 +0300 Subject: [PATCH 03/47] chore(webpack-exodus): eslint --- src/Client.js | 4 ++-- src/util/Injected/Store.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Client.js b/src/Client.js index af21f0624b..14a78047d2 100644 --- a/src/Client.js +++ b/src/Client.js @@ -328,9 +328,9 @@ class Client extends EventEmitter { }); const version = await this.getWWebVersion(); - const isCometOrAbove = parseInt(version.split(".")?.[1]) >= 3000; + const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000; - if (this.options.webVersionCache.type === "local" && this.currentIndexHtml) { + if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) { const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions); diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index a34d6a1869..d9decfc126 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -2,6 +2,7 @@ exports.ExposeStore = () => { if (!require) { + //eslint-disable-next-line no-global-assign require = window.require; } @@ -92,7 +93,7 @@ exports.ExposeStore = () => { id: e }); }; - window.Store.Chat.findImpl = window.Store.Chat._find + window.Store.Chat.findImpl = window.Store.Chat._find; } /** @@ -116,4 +117,4 @@ exports.ExposeStore = () => { window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); -} \ No newline at end of file +}; \ No newline at end of file From eda1920387dcb2237ccebd0f7c8a1f3c9473d962 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 3 Apr 2024 13:40:00 +0300 Subject: [PATCH 04/47] fix(webpack-exodus): Fixed Store --- src/util/Injected/Store.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index d9decfc126..42a3c78126 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -45,7 +45,7 @@ exports.ExposeStore = () => { window.Store.EphemeralFields = require('WAWebGetEphemeralFieldsMsgActionsUtils'); window.Store.MsgActionChecks = require('WAWebMsgActionCapability'); window.Store.QuotedMsg = require('WAWebQuotedMsgModelUtils'); - window.Store.LinkPreview = require('WAWebMsgGetters'); + window.Store.LinkPreview = require('WAWebLinkPreviewChatAction'); window.Store.Socket = require('WADeprecatedSendIq'); window.Store.SocketWap = require('WAWap'); window.Store.SearchContext = require('WAWebChatMessageSearch').getSearchContext; @@ -70,20 +70,20 @@ exports.ExposeStore = () => { ...require('WAWebContactProfilePicThumbBridge') }; window.Store.GroupParticipants = { - ...require('promoteParticipants'), - ...require('sendAddParticipantsRPC') + ...require('WAWebGroupsParticipantsApi'), + ...require('WASmaxGroupsAddParticipantsRPC') }; window.Store.GroupInvite = { - ...require('resetGroupInviteCode'), - ...require('queryGroupInvite') + ...require('WAWebGroupInviteJob'), + ...require('WAWebGroupQueryJob') }; window.Store.GroupInviteV4 = { - ...require('queryGroupInviteV4'), - ...require('sendGroupInviteMessage') + ...require('WAWebGroupInviteV4Job'), + ...require('WAWebChatSendMessages') }; window.Store.MembershipRequestUtils = { - ...require('getMembershipApprovalRequests'), - ...require('sendMembershipRequestsActionRPC') + ...require('WAWebApiMembershipApprovalRequestStore'), + ...require('WASmaxGroupsMembershipRequestsActionRPC') }; if (!window.Store.Chat._find || !window.Store.Chat.findImpl) { From 21904efd9536bbe5749f21a48ff1986016fbccd7 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 3 Apr 2024 13:41:36 +0300 Subject: [PATCH 05/47] fix(webpack-exodus): slight store fix --- src/util/Injected/Store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index 42a3c78126..c05a6ff01d 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -14,7 +14,7 @@ exports.ExposeStore = () => { window.Store.DownloadManager = require('WAWebDownloadManager').downloadManager; window.Store.GroupQueryAndUpdate = require('WAWebGroupQueryJob').queryAndUpdateGroupMetadataById; window.Store.MediaPrep = require('WAWebPrepRawMedia'); - window.Store.MediaObject = require('WAWebMediaStorage').getOrCreateMediaObject; + window.Store.MediaObject = require('WAWebMediaStorage'); window.Store.MediaTypes = require('WAWebMmsMediaTypes'); window.Store.MediaUpload = require('WAWebMediaMmsV4Upload'); window.Store.MsgKey = require('WAWebMsgKey'); From 59698922705a454341fe996dca26be7825671c91 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 3 Apr 2024 13:52:58 +0300 Subject: [PATCH 06/47] chore(webpack-exodus): fix example so that I don't crash on every load --- example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.js b/example.js index dcefe32ed6..6daf1b0305 100644 --- a/example.js +++ b/example.js @@ -418,7 +418,7 @@ client.on('message', async msg => { requesterIds: ['number1@c.us', 'number2@c.us'], sleep: null }); - } else { + } else if (msg.body === '!pin') { /** * Pins a message in a chat, a method takes a number in seconds for the message to be pinned. * WhatsApp default values for duration to pass to the method are: From 1df7e4fc0402d4ec6858c25b6d7b6bc7019f1eb4 Mon Sep 17 00:00:00 2001 From: alechkos <93551621+alechkos@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:29:00 +0300 Subject: [PATCH 07/47] chore: eslint fix --- example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.js b/example.js index 6daf1b0305..89ea9e4376 100644 --- a/example.js +++ b/example.js @@ -418,7 +418,7 @@ client.on('message', async msg => { requesterIds: ['number1@c.us', 'number2@c.us'], sleep: null }); - } else if (msg.body === '!pin') { + } else if (msg.body === '!pinmsg') { /** * Pins a message in a chat, a method takes a number in seconds for the message to be pinned. * WhatsApp default values for duration to pass to the method are: From 77f9c189a22cb1eb61bb7ded1d88bed4c092fceb Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 3 Apr 2024 15:29:52 +0300 Subject: [PATCH 08/47] fix(webpack-exodus): prevent confusion with build systems --- src/util/Injected/Store.js | 139 ++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 72 deletions(-) diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index c05a6ff01d..875a6630d9 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -1,89 +1,84 @@ 'use strict'; exports.ExposeStore = () => { - if (!require) { - //eslint-disable-next-line no-global-assign - require = window.require; - } - - window.Store = Object.assign({}, require('WAWebCollections')); - window.Store.AppState = require('WAWebSocketModel').Socket; - window.Store.BlockContact = require('WAWebBlockContactAction'); - window.Store.Conn = require('WAWebConnModel').Conn; - window.Store.Cmd = require('WAWebCmd').Cmd; - window.Store.DownloadManager = require('WAWebDownloadManager').downloadManager; - window.Store.GroupQueryAndUpdate = require('WAWebGroupQueryJob').queryAndUpdateGroupMetadataById; - window.Store.MediaPrep = require('WAWebPrepRawMedia'); - window.Store.MediaObject = require('WAWebMediaStorage'); - window.Store.MediaTypes = require('WAWebMmsMediaTypes'); - window.Store.MediaUpload = require('WAWebMediaMmsV4Upload'); - window.Store.MsgKey = require('WAWebMsgKey'); - window.Store.NumberInfo = require('WAPhoneUtils'); - window.Store.OpaqueData = require('WAWebMediaOpaqueData'); - window.Store.QueryProduct = require('WAWebBizProductCatalogBridge'); - window.Store.QueryOrder = require('WAWebBizQueryOrderJob'); - window.Store.SendClear = require('WAWebChatClearBridge'); - window.Store.SendDelete = require('WAWebDeleteChatAction'); - window.Store.SendMessage = require('WAWebSendMsgChatAction'); - window.Store.EditMessage = require('WAWebSendMessageEditAction'); - window.Store.SendSeen = require('WAWebUpdateUnreadChatAction'); - window.Store.User = require('WAWebUserPrefsMeUser'); - window.Store.ContactMethods = require('WAWebContactGetters'); - window.Store.UploadUtils = require('WAWebUploadManager'); - window.Store.UserConstructor = require('WAWebWid'); - window.Store.Validators = require('WALinkify'); - window.Store.VCard = require('WAWebFrontendVcardUtils'); - window.Store.WidFactory = require('WAWebWidFactory'); - window.Store.ProfilePic = require('WAWebContactProfilePicThumbBridge'); - window.Store.PresenceUtils = require('WAWebPresenceChatAction'); - window.Store.ChatState = require('WAWebChatStateBridge'); - window.Store.findCommonGroups = require('WAWebFindCommonGroupsContactAction').findCommonGroups; - window.Store.StatusUtils = require('WAWebContactStatusBridge'); - window.Store.ConversationMsgs = require('WAWebChatLoadMessages'); - window.Store.sendReactionToMsg = require('WAWebSendReactionMsgAction').sendReactionToMsg; - window.Store.createOrUpdateReactionsModule = require('WAWebDBCreateOrUpdateReactions'); - window.Store.EphemeralFields = require('WAWebGetEphemeralFieldsMsgActionsUtils'); - window.Store.MsgActionChecks = require('WAWebMsgActionCapability'); - window.Store.QuotedMsg = require('WAWebQuotedMsgModelUtils'); - window.Store.LinkPreview = require('WAWebLinkPreviewChatAction'); - window.Store.Socket = require('WADeprecatedSendIq'); - window.Store.SocketWap = require('WAWap'); - window.Store.SearchContext = require('WAWebChatMessageSearch').getSearchContext; - window.Store.DrawerManager = require('WAWebDrawerManager').DrawerManager; - window.Store.LidUtils = require('WAWebApiContact'); - window.Store.WidToJid = require('WAWebWidToJid'); - window.Store.JidToWid = require('WAWebJidToWid'); - window.Store.getMsgInfo = require('WAWebApiMessageInfoStore').queryMsgInfo; - window.Store.pinUnpinMsg = require('WAWebSendPinMessageAction').sendPinInChatMsg; - window.Store.QueryExist = require('WAWebQueryExistsJob').queryWidExists; - window.Store.ReplyUtils = require('WAWebMsgReply'); - window.Store.Settings = require('WAWebUserPrefsGeneral'); + window.Store = Object.assign({}, window.require('WAWebCollections')); + window.Store.AppState = window.require('WAWebSocketModel').Socket; + window.Store.BlockContact = window.require('WAWebBlockContactAction'); + window.Store.Conn = window.require('WAWebConnModel').Conn; + window.Store.Cmd = window.require('WAWebCmd').Cmd; + window.Store.DownloadManager = window.require('WAWebDownloadManager').downloadManager; + window.Store.GroupQueryAndUpdate = window.require('WAWebGroupQueryJob').queryAndUpdateGroupMetadataById; + window.Store.MediaPrep = window.require('WAWebPrepRawMedia'); + window.Store.MediaObject = window.require('WAWebMediaStorage'); + window.Store.MediaTypes = window.require('WAWebMmsMediaTypes'); + window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload'); + window.Store.MsgKey = window.require('WAWebMsgKey'); + window.Store.NumberInfo = window.require('WAPhoneUtils'); + window.Store.OpaqueData = window.require('WAWebMediaOpaqueData'); + window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge'); + window.Store.QueryOrder = window.require('WAWebBizQueryOrderJob'); + window.Store.SendClear = window.require('WAWebChatClearBridge'); + window.Store.SendDelete = window.require('WAWebDeleteChatAction'); + window.Store.SendMessage = window.require('WAWebSendMsgChatAction'); + window.Store.EditMessage = window.require('WAWebSendMessageEditAction'); + window.Store.SendSeen = window.require('WAWebUpdateUnreadChatAction'); + window.Store.User = window.require('WAWebUserPrefsMeUser'); + window.Store.ContactMethods = window.require('WAWebContactGetters'); + window.Store.UploadUtils = window.require('WAWebUploadManager'); + window.Store.UserConstructor = window.require('WAWebWid'); + window.Store.Validators = window.require('WALinkify'); + window.Store.VCard = window.require('WAWebFrontendVcardUtils'); + window.Store.WidFactory = window.require('WAWebWidFactory'); + window.Store.ProfilePic = window.require('WAWebContactProfilePicThumbBridge'); + window.Store.PresenceUtils = window.require('WAWebPresenceChatAction'); + window.Store.ChatState = window.require('WAWebChatStateBridge'); + window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups; + window.Store.StatusUtils = window.require('WAWebContactStatusBridge'); + window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages'); + window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg; + window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions'); + window.Store.EphemeralFields = window.require('WAWebGetEphemeralFieldsMsgActionsUtils'); + window.Store.MsgActionChecks = window.require('WAWebMsgActionCapability'); + window.Store.QuotedMsg = window.require('WAWebQuotedMsgModelUtils'); + window.Store.LinkPreview = window.require('WAWebLinkPreviewChatAction'); + window.Store.Socket = window.require('WADeprecatedSendIq'); + window.Store.SocketWap = window.require('WAWap'); + window.Store.SearchContext = window.require('WAWebChatMessageSearch').getSearchContext; + window.Store.DrawerManager = window.require('WAWebDrawerManager').DrawerManager; + window.Store.LidUtils = window.require('WAWebApiContact'); + window.Store.WidToJid = window.require('WAWebWidToJid'); + window.Store.JidToWid = window.require('WAWebJidToWid'); + window.Store.getMsgInfo = window.require('WAWebApiMessageInfoStore').queryMsgInfo; + window.Store.pinUnpinMsg = window.require('WAWebSendPinMessageAction').sendPinInChatMsg; + window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists; + window.Store.ReplyUtils = window.require('WAWebMsgReply'); + window.Store.Settings = window.require('WAWebUserPrefsGeneral'); window.Store.StickerTools = { - ...require('WAWebImageUtils'), - ...require('WAWebAddWebpMetadata') + ...window.require('WAWebImageUtils'), + ...window.require('WAWebAddWebpMetadata') }; window.Store.GroupUtils = { - ...require('WAWebGroupCreateJob'), - ...require('WAWebGroupModifyInfoJob'), - ...require('WAWebExitGroupAction'), - ...require('WAWebContactProfilePicThumbBridge') + ...window.require('WAWebGroupCreateJob'), + ...window.require('WAWebGroupModifyInfoJob'), + ...window.require('WAWebExitGroupAction'), + ...window.require('WAWebContactProfilePicThumbBridge') }; window.Store.GroupParticipants = { - ...require('WAWebGroupsParticipantsApi'), - ...require('WASmaxGroupsAddParticipantsRPC') + ...window.require('WAWebGroupsParticipantsApi'), + ...window.require('WASmaxGroupsAddParticipantsRPC') }; window.Store.GroupInvite = { - ...require('WAWebGroupInviteJob'), - ...require('WAWebGroupQueryJob') + ...window.require('WAWebGroupInviteJob'), + ...window.require('WAWebGroupQueryJob') }; window.Store.GroupInviteV4 = { - ...require('WAWebGroupInviteV4Job'), - ...require('WAWebChatSendMessages') + ...window.require('WAWebGroupInviteV4Job'), + ...window.require('WAWebChatSendMessages') }; window.Store.MembershipRequestUtils = { - ...require('WAWebApiMembershipApprovalRequestStore'), - ...require('WASmaxGroupsMembershipRequestsActionRPC') + ...window.require('WAWebApiMembershipApprovalRequestStore'), + ...window.require('WASmaxGroupsMembershipRequestsActionRPC') }; if (!window.Store.Chat._find || !window.Store.Chat.findImpl) { @@ -108,7 +103,7 @@ exports.ExposeStore = () => { * @param {Function} callback Modified function */ window.injectToFunction = (target, callback) => { - const module = require(target.module); + const module = window.require(target.module); const originalFunction = module[target.function]; const modifiedFunction = (...args) => callback(originalFunction, ...args); module[target.function] = modifiedFunction; From 5b1e9116c4c02d2c6f4f23f1792aad1aadab5e86 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 3 Apr 2024 18:38:44 +0300 Subject: [PATCH 09/47] fix(webpack-exodus): Add proper groups module --- example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.js b/example.js index 89ea9e4376..fca5bcfa3d 100644 --- a/example.js +++ b/example.js @@ -5,7 +5,7 @@ const client = new Client({ // proxyAuthentication: { username: 'username', password: 'password' }, puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], - headless: false + headless: true } }); From 78dc0555a3802ef377db9e112a7e601af600f11c Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 4 Apr 2024 00:37:53 +0300 Subject: [PATCH 10/47] fix(webpack-exodus): Fix order queries --- src/util/Injected/Store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index 875a6630d9..0611444298 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -16,7 +16,7 @@ exports.ExposeStore = () => { window.Store.NumberInfo = window.require('WAPhoneUtils'); window.Store.OpaqueData = window.require('WAWebMediaOpaqueData'); window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge'); - window.Store.QueryOrder = window.require('WAWebBizQueryOrderJob'); + window.Store.QueryOrder = window.require('WAWebBizOrderBridge'); window.Store.SendClear = window.require('WAWebChatClearBridge'); window.Store.SendDelete = window.require('WAWebDeleteChatAction'); window.Store.SendMessage = window.require('WAWebSendMsgChatAction'); @@ -65,7 +65,7 @@ exports.ExposeStore = () => { ...window.require('WAWebContactProfilePicThumbBridge') }; window.Store.GroupParticipants = { - ...window.require('WAWebGroupsParticipantsApi'), + ...window.require('WAWebModifyParticipantsGroupAction'), ...window.require('WASmaxGroupsAddParticipantsRPC') }; window.Store.GroupInvite = { From 269e44e117cd74af1714858a38d8168aeeae220b Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Wed, 10 Apr 2024 00:15:16 +0300 Subject: [PATCH 11/47] fix(webpack-exodus): Bad export name --- src/util/Injected/LegacyStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Injected/LegacyStore.js b/src/util/Injected/LegacyStore.js index 57c504d45c..41a29b75cc 100644 --- a/src/util/Injected/LegacyStore.js +++ b/src/util/Injected/LegacyStore.js @@ -3,7 +3,7 @@ //TODO: To be removed y version 2.3000.x hard release // Exposes the internal Store to the WhatsApp Web client -exports.ExposeStore = (moduleRaidStr) => { +exports.ExposeLegacyStore = (moduleRaidStr) => { eval('var moduleRaid = ' + moduleRaidStr); // eslint-disable-next-line no-undef window.mR = moduleRaid(); From 85db3d2dd16422ecea5a083ea8bd90f2e2758f9b Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 14 Apr 2024 04:36:23 +0300 Subject: [PATCH 12/47] feat(webpack-exodus): Re-injection, No more selectors, Pairing Code auth --- example.js | 13 +- index.d.ts | 14 +- src/Client.js | 981 +++++++++--------- src/structures/GroupChat.js | 2 +- src/util/Injected/AuthStore/AuthStore.js | 17 + .../Injected/AuthStore/LegacyAuthStore.js | 15 + src/util/Injected/LegacyStore.js | 7 +- src/util/Injected/Utils.js | 46 +- 8 files changed, 571 insertions(+), 524 deletions(-) create mode 100644 src/util/Injected/AuthStore/AuthStore.js create mode 100644 src/util/Injected/AuthStore/LegacyAuthStore.js diff --git a/example.js b/example.js index fca5bcfa3d..192460dab2 100644 --- a/example.js +++ b/example.js @@ -9,15 +9,26 @@ const client = new Client({ } }); +// client initialize does not finish at ready now. client.initialize(); client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); -client.on('qr', (qr) => { +// Pairing code only needs to be requested once +let pairingCodeRequested = false; +client.on('qr', async (qr) => { // NOTE: This event will not be fired if a session is specified. console.log('QR RECEIVED', qr); + + // paiuting code example + const pairingCodeEnabled = false; + if (pairingCodeEnabled && !pairingCodeRequested) { + const pairingCode = await client.requestPairingCode("96170100100"); // enter the target phone number + console.log("Pairing code enabled, code: "+ pairingCode); + pairingCodeRequested = true; + } }); client.on('authenticated', () => { diff --git a/index.d.ts b/index.d.ts index 329a419bf4..95eaf9cff3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'events' import { RequestInit } from 'node-fetch' import * as puppeteer from 'puppeteer' +import InterfaceController from './src/util/InterfaceController' declare namespace WAWebJS { @@ -17,6 +18,9 @@ declare namespace WAWebJS { /** Puppeteer browser running WhatsApp Web */ pupBrowser?: puppeteer.Browser + /** Client interactivity interface */ + interface?: InterfaceController + /**Accepts an invitation to join a group */ acceptInvite(inviteCode: string): Promise @@ -111,6 +115,14 @@ declare namespace WAWebJS { */ muteChat(chatId: string, unmuteDate?: Date): Promise + /** + * Request authentication via pairing code instead of QR code + * @param phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) + * @param showNotification - Show notification to pair on phone number + * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" + */ + requestPairingCode(phoneNumber: string, showNotification = true): Promise + /** Force reset of connection state for the client */ resetState(): Promise @@ -215,7 +227,7 @@ declare namespace WAWebJS { /** Emitted when the client has been disconnected */ on(event: 'disconnected', listener: ( /** reason that caused the disconnect */ - reason: WAState | "NAVIGATION" + reason: WAState | "LOGOUT" ) => void): this /** Emitted when a user joins the chat via invite link or is added by an admin */ diff --git a/src/Client.js b/src/Client.js index 14a78047d2..f355c4d172 100644 --- a/src/Client.js +++ b/src/Client.js @@ -7,9 +7,11 @@ const moduleRaid = require('@pedroslopez/moduleraid/moduleraid'); const Util = require('./util/Util'); const InterfaceController = require('./util/InterfaceController'); const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants'); +const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore'); const { ExposeStore } = require('./util/Injected/Store'); -const { LoadUtils } = require('./util/Injected/Utils'); +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'); @@ -70,19 +72,201 @@ class Client extends EventEmitter { this.authStrategy.setup(this); + /** + * @type {puppeteer.Browser} + */ this.pupBrowser = null; + /** + * @type {puppeteer.Page} + */ this.pupPage = null; this.currentIndexHtml = null; Util.setFfmpegPath(this.options.ffmpegPath); } + /** + * Injection logic + * Private function + * @property {boolean} reinject is this a reinject? + */ + async inject(reinject = false) { + await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs}); + + const version = await this.getWWebVersion(); + const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000; + + if (isCometOrAbove) { + await this.pupPage.evaluate(ExposeAuthStore); + } else { + await this.pupPage.evaluate(ExposeLegacyAuthStore, moduleRaid.toString()); + } + + const needAuthentication = await this.pupPage.evaluate(async () => { + let state = window.AuthStore.AppState.state; + + if (state === "OPENING" || state === "UNLAUNCHED" || state === "PAIRING") { + // wait till state changes + await new Promise(r => { + window.AuthStore.AppState.on('change:state', function waitTillInit(_AppState, state) { + if (state !== "OPENING" && state !== "UNLAUNCHED" && state !== "PAIRING") { + window.AuthStore.AppState.off('change:state', waitTillInit); + r(); + } + }); + }); + } + state = window.AuthStore.AppState.state; + return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE'; + }); + + if (needAuthentication) { + const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); + + if(failed) { + /** + * Emitted when there has been an error while trying to restore an existing session + * @event Client#auth_failure + * @param {string} message + */ + this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload); + await this.destroy(); + if (restart) { + // session restore failed so try again but without session to force new authentication + return this.initialize(); + } + return; + } + + // Register qr events + let qrRetries = 0; + if (!reinject) { + await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => { + /** + * Emitted when a QR code is received + * @event Client#qr + * @param {string} qr QR Code + */ + this.emit(Events.QR_RECEIVED, qr); + if (this.options.qrMaxRetries > 0) { + qrRetries++; + if (qrRetries > this.options.qrMaxRetries) { + this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); + await this.destroy(); + } + } + }); + } + + + await this.pupPage.evaluate(async () => { + const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); + const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get(); + const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey); + const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey); + const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey(); + const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; + const getQR = (ref) => ref + "," + staticKeyB64 + "," + identityKeyB64 + "," + advSecretKey + "," + platform; + + window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr + window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes + }); + }; + + if (!reinject) { + let lastState = ""; + await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => { + if (state == 'CONNECTED' && lastState != 'CONNECTED') { + + const authEventPayload = await this.authStrategy.getAuthEventPayload(); + /** + * Emitted when authentication is successful + * @event Client#authenticated + */ + this.emit(Events.AUTHENTICATED, authEventPayload); + + const injected = await this.pupPage.evaluate(async () => { + return typeof window.Store !== 'undefined' && 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 (isCometOrAbove) { + await this.pupPage.evaluate(ExposeStore); + } else { + await this.pupPage.evaluate(ExposeLegacyStore); + } + + // Check window.Store Injection + await this.pupPage.waitForFunction('window.Store != undefined'); + + /** + * Current connection information + * @type {ClientInfo} + */ + this.info = new ClientInfo(this, await this.pupPage.evaluate(() => { + return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; + })); + + this.interface = new InterfaceController(this); + + //Load util functions (serializers, helper functions) + await this.pupPage.evaluate(LoadUtils); + + await this.attachEventListeners(reinject); + reinject = true; + } + /** + * Emitted when the client has initialized and is ready to receive messages. + * @event Client#ready + */ + this.emit(Events.READY); + this.authStrategy.afterAuthReady(); + } else if (state == 'UNPAIRED_IDLE') { + // refresh qr code + window.Store.Cmd.refreshQR(); + } + console.log(state) + lastState = state; + }); + + await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => { + this.emit(Events.LOADING_SCREEN, percent, "WhatsApp"); // Message is hardcoded as "WhatsApp" for now + }); + }; + + await this.pupPage.evaluate(() => { + window.AuthStore.AppState.on('change:stream', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); }); + window.AuthStore.Conn.on("me_ready", (...args) => {console.log("me_ready", ...args);}); + window.AuthStore.Cmd.on("offline_progress_update", () => { + window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress()); + }); + }); + } /** * Sets up events and requirements, kicks off authentication request */ async initialize() { - let [browser, page] = [null, null]; + + let + /** + * @type {puppeteer.Browser} + */ + browser, + /** + * @type {puppeteer.Page} + */ + page; + + browser = null; + page = null; await this.authStrategy.beforeBrowserInitialized(); @@ -116,7 +300,7 @@ class Client extends EventEmitter { await this.initWebVersionCache(); // ocVersion (isOfficialClient patch) - // remove on after 2.3000.x + // remove after 2.3000.x hard release await page.evaluateOnNewDocument(() => { const originalError = Error; //eslint-disable-next-line no-global-assign @@ -134,565 +318,352 @@ class Client extends EventEmitter { referer: 'https://whatsapp.com/' }); - await page.evaluate(`function getElementByXpath(path) { - return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; - }`); - - let lastPercent = null, - lastPercentMessage = null; + await this.inject(); - await page.exposeFunction('loadingScreen', async (percent, message) => { - if (lastPercent !== percent || lastPercentMessage !== message) { - this.emit(Events.LOADING_SCREEN, percent, message); - lastPercent = percent; - lastPercentMessage = message; + this.pupPage.on('framenavigated', async (frame) => { + if(frame.url().includes('post_logout=1')) { + this.emit(Events.DISCONNECTED, 'LOGOUT'); + await this.authStrategy.disconnect(); } + await this.inject(true); }); + } - await page.evaluate( - async function (selectors) { - var observer = new MutationObserver(function () { - let progressBar = window.getElementByXpath( - selectors.PROGRESS - ); - let progressMessage = window.getElementByXpath( - selectors.PROGRESS_MESSAGE - ); + /** + * Request authentication via pairing code instead of QR code + * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) + * @param {boolean} showNotification - Show notification to pair on phone number + * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" + */ + async requestPairingCode(phoneNumber, showNotification = true) { + return await this.pupPage.evaluate(async (phoneNumber, showNotification) => { + window.AuthStore.PairingCodeLinkUtils.setPairingType("ALT_DEVICE_LINKING"); + await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); + return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); + }, phoneNumber, showNotification); + } - if (progressBar) { - window.loadingScreen( - progressBar.value, - progressMessage.innerText - ); + /** + * Attach event listeners to WA Web + * Private function + * @property {boolean} reinject is this a reinject? + */ + async attachEventListeners(reinject = false) { + if (!reinject) { + await this.pupPage.exposeFunction('onAddMessageEvent', msg => { + if (msg.type === 'gp2') { + const notification = new GroupNotification(this, msg); + if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) { + /** + * Emitted when a user joins the chat via invite link or is added by an admin. + * @event Client#group_join + * @param {GroupNotification} notification GroupNotification with more information about the action + */ + this.emit(Events.GROUP_JOIN, notification); + } else if (msg.subtype === 'remove' || msg.subtype === 'leave') { + /** + * Emitted when a user leaves the chat or is removed by an admin. + * @event Client#group_leave + * @param {GroupNotification} notification GroupNotification with more information about the action + */ + this.emit(Events.GROUP_LEAVE, notification); + } else if (msg.subtype === 'promote' || msg.subtype === 'demote') { + /** + * Emitted when a current user is promoted to an admin or demoted to a regular user. + * @event Client#group_admin_changed + * @param {GroupNotification} notification GroupNotification with more information about the action + */ + this.emit(Events.GROUP_ADMIN_CHANGED, notification); + } else if (msg.subtype === 'created_membership_requests') { + /** + * Emitted when some user requested to join the group + * that has the membership approval mode turned on + * @event Client#group_membership_request + * @param {GroupNotification} notification GroupNotification with more information about the action + * @param {string} notification.chatId The group ID the request was made for + * @param {string} notification.author The user ID that made a request + * @param {number} notification.timestamp The timestamp the request was made at + */ + this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification); + } else { + /** + * Emitted when group settings are updated, such as subject, description or picture. + * @event Client#group_update + * @param {GroupNotification} notification GroupNotification with more information about the action + */ + this.emit(Events.GROUP_UPDATE, notification); } - }); - - observer.observe(document, { - attributes: true, - childList: true, - characterData: true, - subtree: true, - }); - }, - { - PROGRESS: '//*[@id=\'app\']/div/div/div[2]/progress', - PROGRESS_MESSAGE: '//*[@id=\'app\']/div/div/div[3]', - } - ); - - const INTRO_IMG_SELECTOR = '[data-icon=\'search\']'; - const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas'; - - // Checks which selector appears first - const needAuthentication = await Promise.race([ - new Promise(resolve => { - page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs }) - .then(() => resolve(false)) - .catch((err) => resolve(err)); - }), - new Promise(resolve => { - page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs }) - .then(() => resolve(true)) - .catch((err) => resolve(err)); - }) - ]); - - // Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race; - if (needAuthentication instanceof Error) throw needAuthentication; - - // Scan-qrcode selector was found. Needs authentication - if (needAuthentication) { - const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded(); - if(failed) { - /** - * Emitted when there has been an error while trying to restore an existing session - * @event Client#auth_failure - * @param {string} message - */ - this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload); - await this.destroy(); - if (restart) { - // session restore failed so try again but without session to force new authentication - return this.initialize(); + return; } - return; - } - const QR_CONTAINER = 'div[data-ref]'; - const QR_RETRY_BUTTON = 'div[data-ref] > span > button'; - let qrRetries = 0; - await page.exposeFunction('qrChanged', async (qr) => { + const message = new Message(this, msg); + /** - * Emitted when a QR code is received - * @event Client#qr - * @param {string} qr QR Code - */ - this.emit(Events.QR_RECEIVED, qr); - if (this.options.qrMaxRetries > 0) { - qrRetries++; - if (qrRetries > this.options.qrMaxRetries) { - this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); - await this.destroy(); - } - } - }); + * Emitted when a new message is created, which may include the current user's own messages. + * @event Client#message_create + * @param {Message} message The message that was created + */ + this.emit(Events.MESSAGE_CREATE, message); - await page.evaluate(function (selectors) { - const qr_container = document.querySelector(selectors.QR_CONTAINER); - window.qrChanged(qr_container.dataset.ref); + if (msg.id.fromMe) return; - const obs = new MutationObserver((muts) => { - muts.forEach(mut => { - // Listens to qr token change - if (mut.type === 'attributes' && mut.attributeName === 'data-ref') { - window.qrChanged(mut.target.dataset.ref); - } - // Listens to retry button, when found, click it - else if (mut.type === 'childList') { - const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON); - if (retry_button) retry_button.click(); - } - }); - }); - obs.observe(qr_container.parentElement, { - subtree: true, - childList: true, - attributes: true, - attributeFilter: ['data-ref'], - }); - }, { - QR_CONTAINER, - QR_RETRY_BUTTON + /** + * Emitted when a new message is received. + * @event Client#message + * @param {Message} message The message that was received + */ + this.emit(Events.MESSAGE_RECEIVED, message); }); - // Wait for code scan - try { - await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 }); - } catch(error) { - if ( - error.name === 'ProtocolError' && - error.message && - error.message.match(/Target closed/) - ) { - // something has called .destroy() while waiting - return; - } - - throw error; - } - - } - - await page.evaluate(() => { - /** - * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version. - * @param {string} lOperand The left operand for the WWeb version string to compare with - * @param {string} operator The comparison operator - * @param {string} rOperand The right operand for the WWeb version string to compare with - * @returns {boolean} Boolean value that indicates the result of the comparison - */ - window.compareWwebVersions = (lOperand, operator, rOperand) => { - if (!['>', '>=', '<', '<=', '='].includes(operator)) { - throw new class _ extends Error { - constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } - }('Invalid comparison operator is provided'); + let last_message; - } - 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'); - } + await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => { - lOperand = lOperand.replace(/-beta$/, ''); - rOperand = rOperand.replace(/-beta$/, ''); + 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); + } - while (lOperand.length !== rOperand.length) { - lOperand.length > rOperand.length - ? rOperand = rOperand.concat('0') - : lOperand = lOperand.concat('0'); + /** + * Emitted when a message is deleted for everyone in the chat. + * @event Client#message_revoke_everyone + * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data. + * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data. + * Note that due to the way this data is captured, it may be possible that this param will be undefined. + */ + this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); } - lOperand = Number(lOperand.replace(/\./g, '')); - rOperand = Number(rOperand.replace(/\./g, '')); - - return ( - operator === '>' ? lOperand > rOperand : - operator === '>=' ? lOperand >= rOperand : - operator === '<' ? lOperand < rOperand : - operator === '<=' ? lOperand <= rOperand : - operator === '=' ? lOperand === rOperand : - false - ); - }; - }); - - const version = await this.getWWebVersion(); - const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000; - - 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 page.evaluate(ExposeStore); - } else { - await page.evaluate(ExposeLegacyStore, moduleRaid.toString()); - } + }); + await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => { - const authEventPayload = await this.authStrategy.getAuthEventPayload(); + if (msg.type !== 'revoked') { + last_message = msg; + } - /** - * Emitted when authentication is successful - * @event Client#authenticated - */ - this.emit(Events.AUTHENTICATED, authEventPayload); + /** + * The event notification that is received when one of + * the group participants changes their phone number. + */ + const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify'; - // Check window.Store Injection - await page.waitForFunction('window.Store != undefined'); + /** + * The event notification that is received when one of + * the contacts changes their phone number. + */ + const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number'; - await page.evaluate(async () => { - // safely unregister service workers - const registrations = await navigator.serviceWorker.getRegistrations(); - for (let registration of registrations) { - registration.unregister(); - } - }); + if (isParticipant || isContact) { + /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */ + const message = new Message(this, msg); - //Load util functions (serializers, helper functions) - await page.evaluate(LoadUtils); + const newId = isParticipant ? msg.recipients[0] : msg.to; + const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId); - // Expose client info - /** - * Current connection information - * @type {ClientInfo} - */ - this.info = new ClientInfo(this, await page.evaluate(() => { - return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; - })); - - // Add InterfaceController - this.interface = new InterfaceController(this); - - // Register events - await page.exposeFunction('onAddMessageEvent', msg => { - if (msg.type === 'gp2') { - const notification = new GroupNotification(this, msg); - if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) { /** - * Emitted when a user joins the chat via invite link or is added by an admin. - * @event Client#group_join - * @param {GroupNotification} notification GroupNotification with more information about the action + * Emitted when a contact or a group participant changes their phone number. + * @event Client#contact_changed + * @param {Message} message Message with more information about the event. + * @param {String} oldId The user's id (an old one) who changed their phone number + * and who triggered the notification. + * @param {String} newId The user's new id after the change. + * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number. */ - this.emit(Events.GROUP_JOIN, notification); - } else if (msg.subtype === 'remove' || msg.subtype === 'leave') { - /** - * Emitted when a user leaves the chat or is removed by an admin. - * @event Client#group_leave - * @param {GroupNotification} notification GroupNotification with more information about the action - */ - this.emit(Events.GROUP_LEAVE, notification); - } else if (msg.subtype === 'promote' || msg.subtype === 'demote') { - /** - * Emitted when a current user is promoted to an admin or demoted to a regular user. - * @event Client#group_admin_changed - * @param {GroupNotification} notification GroupNotification with more information about the action - */ - this.emit(Events.GROUP_ADMIN_CHANGED, notification); - } else if (msg.subtype === 'created_membership_requests') { - /** - * Emitted when some user requested to join the group - * that has the membership approval mode turned on - * @event Client#group_membership_request - * @param {GroupNotification} notification GroupNotification with more information about the action - * @param {string} notification.chatId The group ID the request was made for - * @param {string} notification.author The user ID that made a request - * @param {number} notification.timestamp The timestamp the request was made at - */ - this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification); - } else { - /** - * Emitted when group settings are updated, such as subject, description or picture. - * @event Client#group_update - * @param {GroupNotification} notification GroupNotification with more information about the action - */ - this.emit(Events.GROUP_UPDATE, notification); + this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact); } - return; - } - - const message = new Message(this, msg); - - /** - * Emitted when a new message is created, which may include the current user's own messages. - * @event Client#message_create - * @param {Message} message The message that was created - */ - this.emit(Events.MESSAGE_CREATE, message); - - if (msg.id.fromMe) return; - - /** - * Emitted when a new message is received. - * @event Client#message - * @param {Message} message The message that was received - */ - this.emit(Events.MESSAGE_RECEIVED, message); - }); + }); - let last_message; + await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => { - await page.exposeFunction('onChangeMessageTypeEvent', (msg) => { + if (!msg.isNewMsg) return; - if (msg.type === 'revoked') { const message = new Message(this, msg); - let revoked_msg; - if (last_message && msg.id.id === last_message.id.id) { - revoked_msg = new Message(this, last_message); - } /** - * Emitted when a message is deleted for everyone in the chat. - * @event Client#message_revoke_everyone - * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data. - * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data. - * Note that due to the way this data is captured, it may be possible that this param will be undefined. + * Emitted when a message is deleted by the current user. + * @event Client#message_revoke_me + * @param {Message} message The message that was revoked */ - this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); - } + this.emit(Events.MESSAGE_REVOKED_ME, message); - }); - - await page.exposeFunction('onChangeMessageEvent', (msg) => { - - if (msg.type !== 'revoked') { - last_message = msg; - } - - /** - * The event notification that is received when one of - * the group participants changes their phone number. - */ - const isParticipant = msg.type === 'gp2' && 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'; + await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => { - if (isParticipant || isContact) { - /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */ const message = new Message(this, msg); - const newId = isParticipant ? msg.recipients[0] : msg.to; - const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId); - /** - * Emitted when a contact or a group participant changes their phone number. - * @event Client#contact_changed - * @param {Message} message Message with more information about the event. - * @param {String} oldId The user's id (an old one) who changed their phone number - * and who triggered the notification. - * @param {String} newId The user's new id after the change. - * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number. + * Emitted when an ack event occurrs on message type. + * @event Client#message_ack + * @param {Message} message The message that was affected + * @param {MessageAck} ack The new ACK value */ - this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact); - } - }); - - await page.exposeFunction('onRemoveMessageEvent', (msg) => { - - if (!msg.isNewMsg) return; - - const message = new Message(this, msg); + this.emit(Events.MESSAGE_ACK, message, ack); - /** - * Emitted when a message is deleted by the current user. - * @event Client#message_revoke_me - * @param {Message} message The message that was revoked - */ - this.emit(Events.MESSAGE_REVOKED_ME, message); - - }); + }); - await page.exposeFunction('onMessageAckEvent', (msg, ack) => { + await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{ + const chat = await this.getChatById(data.id); + + /** + * Emitted when the chat unread count changes + */ + this.emit(Events.UNREAD_COUNT, chat); + }); - const message = new Message(this, msg); + await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (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); + const message = new Message(this, msg); - }); + /** + * Emitted when media has been uploaded for a message sent by the client. + * @event Client#media_uploaded + * @param {Message} message The message with media that was uploaded + */ + this.emit(Events.MEDIA_UPLOADED, message); + }); - await page.exposeFunction('onChatUnreadCountEvent', async (data) =>{ - const chat = await this.getChatById(data.id); - - /** - * Emitted when the chat unread count changes - */ - this.emit(Events.UNREAD_COUNT, chat); - }); + await this.pupPage.exposeFunction('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); - await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => { + const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT]; - const message = new Message(this, msg); + if (this.options.takeoverOnConflict) { + ACCEPTED_STATES.push(WAState.CONFLICT); - /** - * Emitted when media has been uploaded for a message sent by the client. - * @event Client#media_uploaded - * @param {Message} message The message with media that was uploaded - */ - this.emit(Events.MEDIA_UPLOADED, message); - }); - - await page.exposeFunction('onAppStateChangedEvent', async (state) => { - - /** - * Emitted when the connection state changes - * @event Client#change_state - * @param {WAState} state the new connection state - */ - this.emit(Events.STATE_CHANGED, state); + if (state === WAState.CONFLICT) { + setTimeout(() => { + this.pupPage.evaluate(() => window.Store.AppState.takeover()); + }, this.options.takeoverTimeoutMs); + } + } - const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT]; + if (!ACCEPTED_STATES.includes(state)) { + /** + * Emitted when the client has been disconnected + * @event Client#disconnected + * @param {WAState|"LOGOUT"} reason reason that caused the disconnect + */ + await this.authStrategy.disconnect(); + this.emit(Events.DISCONNECTED, state); + this.destroy(); + } + }); - if (this.options.takeoverOnConflict) { - ACCEPTED_STATES.push(WAState.CONFLICT); + await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => { + const { battery, plugged } = state; - if (state === WAState.CONFLICT) { - setTimeout(() => { - this.pupPage.evaluate(() => window.Store.AppState.takeover()); - }, this.options.takeoverTimeoutMs); - } - } + if (battery === undefined) return; - if (!ACCEPTED_STATES.includes(state)) { /** - * Emitted when the client has been disconnected - * @event Client#disconnected - * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect + * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. + * @event Client#change_battery + * @param {object} batteryInfo + * @param {number} batteryInfo.battery - The current battery percentage + * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) + * @deprecated */ - await this.authStrategy.disconnect(); - this.emit(Events.DISCONNECTED, state); - this.destroy(); - } - }); + this.emit(Events.BATTERY_CHANGED, { battery, plugged }); + }); - await page.exposeFunction('onBatteryStateChangedEvent', (state) => { - const { battery, plugged } = state; + await this.pupPage.exposeFunction('onIncomingCall', (call) => { + /** + * Emitted when a call is received + * @event Client#incoming_call + * @param {object} call + * @param {number} call.id - Call id + * @param {string} call.peerJid - Who called + * @param {boolean} call.isVideo - if is video + * @param {boolean} call.isGroup - if is group + * @param {boolean} call.canHandleLocally - if we can handle in waweb + * @param {boolean} call.outgoing - if is outgoing + * @param {boolean} call.webClientShouldHandle - If Waweb should handle + * @param {object} call.participants - Participants + */ + const cll = new Call(this, call); + this.emit(Events.INCOMING_CALL, cll); + }); - if (battery === undefined) return; + await this.pupPage.exposeFunction('onReaction', (reactions) => { + for (const reaction of reactions) { + /** + * Emitted when a reaction is sent, received, updated or removed + * @event Client#message_reaction + * @param {object} reaction + * @param {object} reaction.id - Reaction id + * @param {number} reaction.orphan - Orphan + * @param {?string} reaction.orphanReason - Orphan reason + * @param {number} reaction.timestamp - Timestamp + * @param {string} reaction.reaction - Reaction + * @param {boolean} reaction.read - Read + * @param {object} reaction.msgId - Parent message id + * @param {string} reaction.senderId - Sender id + * @param {?number} reaction.ack - Ack + */ - /** - * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device. - * @event Client#change_battery - * @param {object} batteryInfo - * @param {number} batteryInfo.battery - The current battery percentage - * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false) - * @deprecated - */ - this.emit(Events.BATTERY_CHANGED, { battery, plugged }); - }); + this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction)); + } + }); - await page.exposeFunction('onIncomingCall', (call) => { - /** - * Emitted when a call is received - * @event Client#incoming_call - * @param {object} call - * @param {number} call.id - Call id - * @param {string} call.peerJid - Who called - * @param {boolean} call.isVideo - if is video - * @param {boolean} call.isGroup - if is group - * @param {boolean} call.canHandleLocally - if we can handle in waweb - * @param {boolean} call.outgoing - if is outgoing - * @param {boolean} call.webClientShouldHandle - If Waweb should handle - * @param {object} call.participants - Participants - */ - const cll = new Call(this, call); - this.emit(Events.INCOMING_CALL, cll); - }); + await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => { + const _chat = await this.getChatById(chat.id); - await page.exposeFunction('onReaction', (reactions) => { - for (const reaction of reactions) { /** - * Emitted when a reaction is sent, received, updated or removed - * @event Client#message_reaction - * @param {object} reaction - * @param {object} reaction.id - Reaction id - * @param {number} reaction.orphan - Orphan - * @param {?string} reaction.orphanReason - Orphan reason - * @param {number} reaction.timestamp - Timestamp - * @param {string} reaction.reaction - Reaction - * @param {boolean} reaction.read - Read - * @param {object} reaction.msgId - Parent message id - * @param {string} reaction.senderId - Sender id - * @param {?number} reaction.ack - Ack + * Emitted when a chat is removed + * @event Client#chat_removed + * @param {Chat} chat */ - - this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction)); - } - }); - - await page.exposeFunction('onRemoveChatEvent', async (chat) => { - const _chat = await this.getChatById(chat.id); - - /** - * Emitted when a chat is removed - * @event Client#chat_removed - * @param {Chat} chat - */ - this.emit(Events.CHAT_REMOVED, _chat); - }); - - await page.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => { - const _chat = await this.getChatById(chat.id); + this.emit(Events.CHAT_REMOVED, _chat); + }); - /** - * Emitted when a chat is archived/unarchived - * @event Client#chat_archived - * @param {Chat} chat - * @param {boolean} currState - * @param {boolean} prevState - */ - this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState); - }); + await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => { + const _chat = await this.getChatById(chat.id); + + /** + * Emitted when a chat is archived/unarchived + * @event Client#chat_archived + * @param {Chat} chat + * @param {boolean} currState + * @param {boolean} prevState + */ + this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState); + }); - await page.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => { - - if(msg.type === 'revoked'){ - return; - } - /** - * Emitted when messages are edited - * @event Client#message_edit - * @param {Message} message - * @param {string} newBody - * @param {string} prevBody - */ - this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); - }); - - await page.exposeFunction('onAddMessageCiphertextEvent', msg => { + await this.pupPage.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => { + + if(msg.type === 'revoked'){ + return; + } + /** + * Emitted when messages are edited + * @event Client#message_edit + * @param {Message} message + * @param {string} newBody + * @param {string} prevBody + */ + this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); + }); - /** - * Emitted when messages are edited - * @event Client#message_ciphertext - * @param {Message} message - */ - this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); - }); + await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => { + + /** + * Emitted when messages are edited + * @event Client#message_ciphertext + * @param {Message} message + */ + this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); + }); + } - await page.evaluate(() => { + await this.pupPage.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); }); @@ -716,7 +687,6 @@ class Client extends EventEmitter { } }); window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);}); - { const module = window.Store.createOrUpdateReactionsModule; const ogMethod = module.createOrUpdateReactions; @@ -733,24 +703,7 @@ class Client extends EventEmitter { }).bind(module); } }); - - /** - * Emitted when the client has initialized and is ready to receive messages. - * @event Client#ready - */ - this.emit(Events.READY); - this.authStrategy.afterAuthReady(); - - // Disconnect when navigating away when in PAIRING state (detect logout) - this.pupPage.on('framenavigated', async () => { - const appState = await this.getState(); - if(!appState || appState === WAState.PAIRING) { - await this.authStrategy.disconnect(); - this.emit(Events.DISCONNECTED, 'NAVIGATION'); - await this.destroy(); - } - }); - } + } async initWebVersionCache() { const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; @@ -1428,7 +1381,7 @@ class Client extends EventEmitter { comment, await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid) ); - isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') + isInviteV4Sent = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') ? addParticipantResult === 'OK' : addParticipantResult.messageSendResult === 'OK'; } diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 934639de40..12448238da 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -166,7 +166,7 @@ class GroupChat extends Chat { comment, await window.WWebJS.getProfilePicThumbToBase64(groupWid) ); - isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') + isInviteV4Sent = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') ? res === 'OK' : res.messageSendResult === 'OK'; } diff --git a/src/util/Injected/AuthStore/AuthStore.js b/src/util/Injected/AuthStore/AuthStore.js new file mode 100644 index 0000000000..cc7bc7a7ae --- /dev/null +++ b/src/util/Injected/AuthStore/AuthStore.js @@ -0,0 +1,17 @@ +'use strict'; + +exports.ExposeAuthStore = () => { + window.AuthStore = {}; + window.AuthStore.AppState = window.require('WAWebSocketModel').Socket; + window.AuthStore.Cmd = window.require('WAWebCmd').Cmd; + window.AuthStore.Conn = window.require('WAWebConnModel').Conn; + window.AuthStore.OfflineMessageHandler = window.require('WAWebOfflineHandler').OfflineMessageHandler; + window.AuthStore.PairingCodeLinkUtils = window.require('WAWebAltDeviceLinkingApi'); + window.AuthStore.Base64Tools = window.require('WABase64'); + window.AuthStore.RegistrationUtils = { + ...window.require('WAWebCompanionRegClientUtils'), + ...window.require('WAWebAdvSignatureApi'), + ...window.require('WAWebUserPrefsInfoStore'), + ...window.require('WAWebSignalStoreApi'), + }; +} \ No newline at end of file diff --git a/src/util/Injected/AuthStore/LegacyAuthStore.js b/src/util/Injected/AuthStore/LegacyAuthStore.js new file mode 100644 index 0000000000..8ea0cb3bce --- /dev/null +++ b/src/util/Injected/AuthStore/LegacyAuthStore.js @@ -0,0 +1,15 @@ +'use strict'; + +//TODO: To be removed by version 2.3000.x hard release + +exports.ExposeLegacyAuthStore = (moduleRaidStr) => { + eval('var moduleRaid = ' + moduleRaidStr); + // eslint-disable-next-line no-undef + window.mR = moduleRaid(); + window.AuthStore = {}; + window.AuthStore.AppState = window.mR.findModule('Socket')[0].Socket; + window.AuthStore.Cmd = window.mR.findModule('Cmd')[0].Cmd; + window.AuthStore.Conn = window.mR.findModule('Conn')[0].Conn; + window.AuthStore.OfflineMessageHandler = window.mR.findModule('OfflineMessageHandler')[0].OfflineMessageHandler; + window.AuthStore.PairingCodeLinkUtils = window.mR.findModule('initializeAltDeviceLinking')[0]; +} \ No newline at end of file diff --git a/src/util/Injected/LegacyStore.js b/src/util/Injected/LegacyStore.js index 41a29b75cc..e9584d9b7a 100644 --- a/src/util/Injected/LegacyStore.js +++ b/src/util/Injected/LegacyStore.js @@ -1,12 +1,9 @@ 'use strict'; -//TODO: To be removed y version 2.3000.x hard release +//TODO: To be removed by version 2.3000.x hard release // Exposes the internal Store to the WhatsApp Web client -exports.ExposeLegacyStore = (moduleRaidStr) => { - eval('var moduleRaid = ' + moduleRaidStr); - // eslint-disable-next-line no-undef - window.mR = moduleRaid(); +exports.ExposeLegacyStore = () => { window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); window.Store.AppState = window.mR.findModule('Socket')[0].Socket; window.Store.Conn = window.mR.findModule('Conn')[0].Conn; diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index 9f9440641a..f847258271 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -459,7 +459,7 @@ exports.LoadUtils = () => { // 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'); + = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.2327.4'); res.isMe = useOldImplementation ? contact.isMe @@ -828,7 +828,7 @@ exports.LoadUtils = () => { }]; let rpcResult, resultArgs; - const isOldImpl = window.compareWwebVersions(window.Debug.VERSION, '<=', '2.2335.9'); + const isOldImpl = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<=', '2.2335.9'); const data = { name: undefined, code: undefined, @@ -972,4 +972,46 @@ exports.LoadUtils = () => { if (response.messageSendResult === 'OK') return true; return false; }; + + /** + * 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'); + } + + 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 + ); + }; }; From 27621f14ba77ed2f2f51f8e3285d6bd492fee0d5 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 14 Apr 2024 06:35:29 +0300 Subject: [PATCH 13/47] fix(webpack-exodus): Eslint and better login/logout handling --- src/Client.js | 130 ++++++++++++++++++-------------- src/authStrategies/LocalAuth.js | 7 +- 2 files changed, 79 insertions(+), 58 deletions(-) diff --git a/src/Client.js b/src/Client.js index f355c4d172..fec6a3a88c 100644 --- a/src/Client.js +++ b/src/Client.js @@ -82,6 +82,7 @@ class Client extends EventEmitter { this.pupPage = null; this.currentIndexHtml = null; + this.lastLoggedOut = false; Util.setFfmpegPath(this.options.ffmpegPath); } @@ -140,7 +141,10 @@ class Client extends EventEmitter { // Register qr events let qrRetries = 0; - if (!reinject) { + const injected = await this.pupPage.evaluate(() => { + return typeof window.onQRChangedEvent !== 'undefined'; + }); + if (!injected) { await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => { /** * Emitted when a QR code is received @@ -157,7 +161,7 @@ class Client extends EventEmitter { } }); } - + await this.pupPage.evaluate(async () => { const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); @@ -174,79 +178,88 @@ class Client extends EventEmitter { }; if (!reinject) { - let lastState = ""; await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => { - if (state == 'CONNECTED' && lastState != 'CONNECTED') { + if (state == 'UNPAIRED_IDLE') { + // refresh qr code + window.Store.Cmd.refreshQR(); + } + }); - const authEventPayload = await this.authStrategy.getAuthEventPayload(); - /** - * Emitted when authentication is successful - * @event Client#authenticated - */ - this.emit(Events.AUTHENTICATED, authEventPayload); + await this.pupPage.exposeFunction('onAppStateHasSyncedEvent', async () => { + const authEventPayload = await this.authStrategy.getAuthEventPayload(); + /** + * Emitted when authentication is successful + * @event Client#authenticated + */ + this.emit(Events.AUTHENTICATED, authEventPayload); - const injected = await this.pupPage.evaluate(async () => { - return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined'; - }); + const injected = await this.pupPage.evaluate(async () => { + return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined'; + }); - if (!injected) { - if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) { - const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache; - const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions); - - await webCache.persist(this.currentIndexHtml, version); - } + if (!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 { - await this.pupPage.evaluate(ExposeLegacyStore); - } + if (isCometOrAbove) { + await this.pupPage.evaluate(ExposeStore); + } else { + await this.pupPage.evaluate(ExposeLegacyStore); + } - // Check window.Store Injection - await this.pupPage.waitForFunction('window.Store != undefined'); - - /** - * Current connection information - * @type {ClientInfo} - */ - this.info = new ClientInfo(this, await this.pupPage.evaluate(() => { - return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; - })); + // Check window.Store Injection + await this.pupPage.waitForFunction('window.Store != undefined'); + + /** + * Current connection information + * @type {ClientInfo} + */ + this.info = new ClientInfo(this, await this.pupPage.evaluate(() => { + return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() }; + })); - this.interface = new InterfaceController(this); + this.interface = new InterfaceController(this); - //Load util functions (serializers, helper functions) - await this.pupPage.evaluate(LoadUtils); + //Load util functions (serializers, helper functions) + await this.pupPage.evaluate(LoadUtils); - await this.attachEventListeners(reinject); - reinject = true; - } - /** - * Emitted when the client has initialized and is ready to receive messages. - * @event Client#ready - */ - this.emit(Events.READY); - this.authStrategy.afterAuthReady(); - } else if (state == 'UNPAIRED_IDLE') { - // refresh qr code - window.Store.Cmd.refreshQR(); + await this.attachEventListeners(reinject); + reinject = true; } - console.log(state) - lastState = state; + /** + * Emitted when the client has initialized and is ready to receive messages. + * @event Client#ready + */ + this.emit(Events.READY); + this.authStrategy.afterAuthReady(); }); await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => { this.emit(Events.LOADING_SCREEN, percent, "WhatsApp"); // Message is hardcoded as "WhatsApp" for now }); }; - + const logoutCatchInjected = await this.pupPage.evaluate(() => { + return typeof window.onLogoutEvent !== 'undefined'; + }); + if (!logoutCatchInjected) { + await this.pupPage.exposeFunction('onLogoutEvent', async () => { + this.lastLoggedOut = true; + await this.pupPage.waitForNavigation({waitUntil: "load", timeout: 5000}).catch((_) => _); + }); + } await this.pupPage.evaluate(() => { - window.AuthStore.AppState.on('change:stream', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); }); - window.AuthStore.Conn.on("me_ready", (...args) => {console.log("me_ready", ...args);}); + window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); }); + window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); }); window.AuthStore.Cmd.on("offline_progress_update", () => { window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress()); }); + window.AuthStore.Cmd.on("logout", async () => { + await window.onLogoutEvent(); + }) }); } @@ -321,9 +334,12 @@ class Client extends EventEmitter { await this.inject(); this.pupPage.on('framenavigated', async (frame) => { - if(frame.url().includes('post_logout=1')) { + if(frame.url().includes('post_logout=1') || this.lastLoggedOut) { this.emit(Events.DISCONNECTED, 'LOGOUT'); - await this.authStrategy.disconnect(); + await this.authStrategy.logout(); + await this.authStrategy.beforeBrowserInitialized(); + await this.authStrategy.afterBrowserInitialized(); + this.lastLoggedOut = false; } await this.inject(true); }); diff --git a/src/authStrategies/LocalAuth.js b/src/authStrategies/LocalAuth.js index 8309c3d0d5..fb0cca404c 100644 --- a/src/authStrategies/LocalAuth.js +++ b/src/authStrategies/LocalAuth.js @@ -3,6 +3,7 @@ const path = require('path'); const fs = require('fs'); const BaseAuthStrategy = require('./BaseAuthStrategy'); +const Util = require('../util/Util'); /** * Local directory-based authentication @@ -44,7 +45,11 @@ class LocalAuth extends BaseAuthStrategy { async logout() { if (this.userDataDir) { - return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true, force: true }); + await fs.promises.rm(this.userDataDir, { recursive: true, force: true }) + .catch(err => { + return this.logout(); + }); + return; } } From dd759674cb8f0f1dc591330f5ce10618ab41de2e Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 14 Apr 2024 15:30:23 +0300 Subject: [PATCH 14/47] fix(webpack-exodus): 2.24 compatibility --- example.js | 8 +++++++- src/Client.js | 3 +++ src/util/Injected/AuthStore/LegacyAuthStore.js | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/example.js b/example.js index 192460dab2..f4d3fa56ba 100644 --- a/example.js +++ b/example.js @@ -2,10 +2,16 @@ const { Client, Location, Poll, List, Buttons, LocalAuth } = require('./index'); const client = new Client({ authStrategy: new LocalAuth(), + webVersion: "2.2409.2", + webVersionCache: { + type: "remote", + remotePath: + "https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/2.2409.2.html", + }, // proxyAuthentication: { username: 'username', password: 'password' }, puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], - headless: true + headless: false, } }); diff --git a/src/Client.js b/src/Client.js index fec6a3a88c..ee4a6b1483 100644 --- a/src/Client.js +++ b/src/Client.js @@ -208,6 +208,9 @@ class Client extends EventEmitter { 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); } diff --git a/src/util/Injected/AuthStore/LegacyAuthStore.js b/src/util/Injected/AuthStore/LegacyAuthStore.js index 8ea0cb3bce..67e33121ce 100644 --- a/src/util/Injected/AuthStore/LegacyAuthStore.js +++ b/src/util/Injected/AuthStore/LegacyAuthStore.js @@ -12,4 +12,11 @@ exports.ExposeLegacyAuthStore = (moduleRaidStr) => { window.AuthStore.Conn = window.mR.findModule('Conn')[0].Conn; window.AuthStore.OfflineMessageHandler = window.mR.findModule('OfflineMessageHandler')[0].OfflineMessageHandler; window.AuthStore.PairingCodeLinkUtils = window.mR.findModule('initializeAltDeviceLinking')[0]; + window.AuthStore.Base64Tools = window.mR.findModule('encodeB64')[0]; + window.AuthStore.RegistrationUtils = { + ...window.mR.findModule('getCompanionWebClientFromBrowser')[0], + ...window.mR.findModule('verifyKeyIndexListAccountSignature')[0], + ...window.mR.findModule('waNoiseInfo')[0], + ...window.mR.findModule('waSignalStore')[0], + }; } \ No newline at end of file From e7ae65f25791b2df4faa9ed66c3c81eb79737f34 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 14 Apr 2024 15:31:08 +0300 Subject: [PATCH 15/47] revert testing options --- example.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/example.js b/example.js index f4d3fa56ba..15947f64f4 100644 --- a/example.js +++ b/example.js @@ -2,16 +2,10 @@ const { Client, Location, Poll, List, Buttons, LocalAuth } = require('./index'); const client = new Client({ authStrategy: new LocalAuth(), - webVersion: "2.2409.2", - webVersionCache: { - type: "remote", - remotePath: - "https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/2.2409.2.html", - }, // proxyAuthentication: { username: 'username', password: 'password' }, puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], - headless: false, + headless: true, } }); From cad33ec33af6797e07699b5c4fdc9460c25fe0c1 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 14 Apr 2024 15:53:19 +0300 Subject: [PATCH 16/47] chore(webpack-exodus): ESLint --- example.js | 4 ++-- src/Client.js | 22 +++++++++++----------- src/authStrategies/LocalAuth.js | 3 +-- src/util/Injected/AuthStore/AuthStore.js | 2 +- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/example.js b/example.js index 15947f64f4..5275bbe672 100644 --- a/example.js +++ b/example.js @@ -25,8 +25,8 @@ client.on('qr', async (qr) => { // paiuting code example const pairingCodeEnabled = false; if (pairingCodeEnabled && !pairingCodeRequested) { - const pairingCode = await client.requestPairingCode("96170100100"); // enter the target phone number - console.log("Pairing code enabled, code: "+ pairingCode); + const pairingCode = await client.requestPairingCode('96170100100'); // enter the target phone number + console.log('Pairing code enabled, code: '+ pairingCode); pairingCodeRequested = true; } }); diff --git a/src/Client.js b/src/Client.js index ee4a6b1483..0e3271a50e 100644 --- a/src/Client.js +++ b/src/Client.js @@ -106,11 +106,11 @@ class Client extends EventEmitter { const needAuthentication = await this.pupPage.evaluate(async () => { let state = window.AuthStore.AppState.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") { + if (state !== 'OPENING' && state !== 'UNLAUNCHED' && state !== 'PAIRING') { window.AuthStore.AppState.off('change:state', waitTillInit); r(); } @@ -170,12 +170,12 @@ class Client extends EventEmitter { 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 getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform; window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes }); - }; + } if (!reinject) { await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => { @@ -242,27 +242,27 @@ class Client extends EventEmitter { }); await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => { - this.emit(Events.LOADING_SCREEN, percent, "WhatsApp"); // Message is hardcoded as "WhatsApp" for now + this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now }); - }; + } const logoutCatchInjected = await this.pupPage.evaluate(() => { return typeof window.onLogoutEvent !== 'undefined'; }); if (!logoutCatchInjected) { await this.pupPage.exposeFunction('onLogoutEvent', async () => { this.lastLoggedOut = true; - await this.pupPage.waitForNavigation({waitUntil: "load", timeout: 5000}).catch((_) => _); + await this.pupPage.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.AuthStore.Cmd.on('offline_progress_update', () => { window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress()); }); - window.AuthStore.Cmd.on("logout", async () => { + window.AuthStore.Cmd.on('logout', async () => { await window.onLogoutEvent(); - }) + }); }); } @@ -356,7 +356,7 @@ class Client extends EventEmitter { */ async requestPairingCode(phoneNumber, showNotification = true) { return await this.pupPage.evaluate(async (phoneNumber, showNotification) => { - window.AuthStore.PairingCodeLinkUtils.setPairingType("ALT_DEVICE_LINKING"); + window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); }, phoneNumber, showNotification); diff --git a/src/authStrategies/LocalAuth.js b/src/authStrategies/LocalAuth.js index fb0cca404c..360942d293 100644 --- a/src/authStrategies/LocalAuth.js +++ b/src/authStrategies/LocalAuth.js @@ -3,7 +3,6 @@ const path = require('path'); const fs = require('fs'); const BaseAuthStrategy = require('./BaseAuthStrategy'); -const Util = require('../util/Util'); /** * Local directory-based authentication @@ -46,7 +45,7 @@ class LocalAuth extends BaseAuthStrategy { async logout() { if (this.userDataDir) { await fs.promises.rm(this.userDataDir, { recursive: true, force: true }) - .catch(err => { + .catch(() => { return this.logout(); }); return; diff --git a/src/util/Injected/AuthStore/AuthStore.js b/src/util/Injected/AuthStore/AuthStore.js index cc7bc7a7ae..c797aa0544 100644 --- a/src/util/Injected/AuthStore/AuthStore.js +++ b/src/util/Injected/AuthStore/AuthStore.js @@ -14,4 +14,4 @@ exports.ExposeAuthStore = () => { ...window.require('WAWebUserPrefsInfoStore'), ...window.require('WAWebSignalStoreApi'), }; -} \ No newline at end of file +}; \ No newline at end of file From 452b7128be8fdc3d2fa199c093cf1e71ba1c268d Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 14 Apr 2024 15:56:36 +0300 Subject: [PATCH 17/47] chore(webpack-exodus): final eslint tweak (did not commit with last commit for some reason) --- src/util/Injected/AuthStore/LegacyAuthStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Injected/AuthStore/LegacyAuthStore.js b/src/util/Injected/AuthStore/LegacyAuthStore.js index 67e33121ce..c3016f7d17 100644 --- a/src/util/Injected/AuthStore/LegacyAuthStore.js +++ b/src/util/Injected/AuthStore/LegacyAuthStore.js @@ -19,4 +19,4 @@ exports.ExposeLegacyAuthStore = (moduleRaidStr) => { ...window.mR.findModule('waNoiseInfo')[0], ...window.mR.findModule('waSignalStore')[0], }; -} \ No newline at end of file +}; \ No newline at end of file From fac0d81b8472b70d05c3de02b37cfb3190a4580c Mon Sep 17 00:00:00 2001 From: alechkos <93551621+alechkos@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:37:20 +0300 Subject: [PATCH 18/47] style: fix broken link in readme file --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0262a7d94a..ba4b9a7010 100644 --- a/README.md +++ b/README.md @@ -176,10 +176,10 @@ limitations under the License. [nodejs]: https://nodejs.org/en/download/ [examples]: https://github.com/pedroslopez/whatsapp-web.js/blob/master/example.js [auth-strategies]: https://wwebjs.dev/guide/creating-your-bot/authentication.html -[google-chrome]: https://wwebjs.dev/guide/handling-attachments.html#caveat-for-sending-videos-and-gifs +[google-chrome]: https://wwebjs.dev/guide/creating-your-bot/handling-attachments.html#caveat-for-sending-videos-and-gifs [deprecated-video]: https://www.youtube.com/watch?v=hv1R1rLeVVE [gitHub-sponsors]: https://github.com/sponsors/pedroslopez [support-payPal]: https://www.paypal.me/psla/ [digitalocean]: https://m.do.co/c/73f906a36ed4 [contributing]: https://github.com/pedroslopez/whatsapp-web.js/blob/main/CODE_OF_CONDUCT.md -[whatsapp]: https://whatsapp.com \ No newline at end of file +[whatsapp]: https://whatsapp.com From c1d8e0449b0d2178184abd1e2827615654081046 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 28 Apr 2024 09:52:41 +0300 Subject: [PATCH 19/47] fix(webpack-exodus): Fix forwarding messages --- src/structures/Message.js | 5 +---- src/util/Injected/Store.js | 4 ++++ src/util/Injected/Utils.js | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index a7616998ce..6d3a8bde1f 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -439,10 +439,7 @@ class Message extends Base { const chatId = typeof chat === 'string' ? chat : chat.id._serialized; await this.client.pupPage.evaluate(async (msgId, chatId) => { - let msg = window.Store.Msg.get(msgId); - let chat = window.Store.Chat.get(chatId); - - return await chat.forwardMessages([msg]); + return window.WWebJS.forwardMessage(chatId, msgId); }, this.id._serialized, chatId); } diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index 0611444298..dab498398e 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -53,6 +53,10 @@ exports.ExposeStore = () => { window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists; window.Store.ReplyUtils = window.require('WAWebMsgReply'); window.Store.Settings = window.require('WAWebUserPrefsGeneral'); + + window.Store.ForwardUtils = { + ...window.require("WAWebForwardMessagesToChat") + }; window.Store.StickerTools = { ...window.require('WAWebImageUtils'), diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index f847258271..1e0e51d4b3 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -3,6 +3,19 @@ exports.LoadUtils = () => { window.WWebJS = {}; + window.WWebJS.forwardMessage = async (chatId, msgId) => { + // 2.3000> ForwardMessagesFromChats + // < 23000 , chat.forwardMessage + let msg = window.Store.Msg.get(msgId); + let chat = window.Store.Chat.get(chatId); + + if (window.Debug.VERSION > 23000) { + return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], false) + } else { + return chat.forwardMessages([msg]); + } + }; + window.WWebJS.sendSeen = async (chatId) => { let chat = window.Store.Chat.get(chatId); if (chat !== undefined) { From 650cd0dfe5607303c56d36af0e175ca3da2b6c9c Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 28 Apr 2024 09:58:54 +0300 Subject: [PATCH 20/47] fix(webpack-exodus): finish forwarding function --- src/util/Injected/Utils.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index 1e0e51d4b3..16f91d38c5 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -4,13 +4,11 @@ exports.LoadUtils = () => { window.WWebJS = {}; window.WWebJS.forwardMessage = async (chatId, msgId) => { - // 2.3000> ForwardMessagesFromChats - // < 23000 , chat.forwardMessage let msg = window.Store.Msg.get(msgId); let chat = window.Store.Chat.get(chatId); - if (window.Debug.VERSION > 23000) { - return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], false) + if (window.WWebJS.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) { + return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], false); } else { return chat.forwardMessages([msg]); } From 64491bebfec86f09cf9aa8ebd80af1d24b39d4f6 Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Tue, 14 May 2024 02:49:18 -0300 Subject: [PATCH 21/47] Making the ESLint god happy --- src/util/Injected/Store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index dab498398e..2e0c2201e3 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -55,7 +55,7 @@ exports.ExposeStore = () => { window.Store.Settings = window.require('WAWebUserPrefsGeneral'); window.Store.ForwardUtils = { - ...window.require("WAWebForwardMessagesToChat") + ...window.require('WAWebForwardMessagesToChat') }; window.Store.StickerTools = { @@ -116,4 +116,4 @@ exports.ExposeStore = () => { window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); -}; \ No newline at end of file +}; From 975819d0f0707f03d5d86e8cdf3bb3a156ae4aad Mon Sep 17 00:00:00 2001 From: Seow Zhen Jun <56989187+seowzhenjun0126@users.noreply.github.com> Date: Wed, 15 May 2024 05:10:34 +0800 Subject: [PATCH 22/47] Fix window.Store.ProfilePic.profilePicFind is not a function error (#3001) * Change window.Store.ProfilePic.profilePicFind to window.Store.ProfilePic.requestProfilePicFromServer * Prevent breaking v2.2x * refactor: make it more readable --------- Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com> --- src/Client.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client.js b/src/Client.js index 0e3271a50e..d2e3e03b9c 100644 --- a/src/Client.js +++ b/src/Client.js @@ -1209,7 +1209,9 @@ class Client extends EventEmitter { const profilePic = await this.pupPage.evaluate(async contactId => { try { const chatWid = window.Store.WidFactory.createWid(contactId); - return await window.Store.ProfilePic.profilePicFind(chatWid); + return window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0') + ? await window.Store.ProfilePic.profilePicFind(chatWid) + : await window.Store.ProfilePic.requestProfilePicFromServer(chatWid); } catch (err) { if(err.name === 'ServerStatusCodeError') return undefined; throw err; From e4c208cd79d62c1fee5cf02a0b69d1aa7b0e0cac Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Wed, 15 May 2024 14:09:29 -0300 Subject: [PATCH 23/47] Sanitize, improves and fixes --- example.js | 8 +++++++- src/authStrategies/LocalAuth.js | 5 ++--- src/webCache/LocalWebCache.js | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/example.js b/example.js index 5275bbe672..f6224ff50a 100644 --- a/example.js +++ b/example.js @@ -5,7 +5,7 @@ const client = new Client({ // proxyAuthentication: { username: 'username', password: 'password' }, puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], - headless: true, + headless: false, } }); @@ -15,6 +15,12 @@ client.initialize(); client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); +client.pupPage.on("pageerror", function(err) { + console.log("Page error: " + err.toString()); +}); +client.pupPage.on("error", function(err) { + console.log("Page error: " + err.toString()); +}); // Pairing code only needs to be requested once let pairingCodeRequested = false; diff --git a/src/authStrategies/LocalAuth.js b/src/authStrategies/LocalAuth.js index 360942d293..543a6b9ba1 100644 --- a/src/authStrategies/LocalAuth.js +++ b/src/authStrategies/LocalAuth.js @@ -45,10 +45,9 @@ class LocalAuth extends BaseAuthStrategy { async logout() { if (this.userDataDir) { await fs.promises.rm(this.userDataDir, { recursive: true, force: true }) - .catch(() => { - return this.logout(); + .catch((e) => { + throw new Error(e); }); - return; } } diff --git a/src/webCache/LocalWebCache.js b/src/webCache/LocalWebCache.js index 5be049e54c..ec6440f7be 100644 --- a/src/webCache/LocalWebCache.js +++ b/src/webCache/LocalWebCache.js @@ -30,6 +30,7 @@ class LocalWebCache extends WebCache { } async persist(indexHtml, version) { + version = (version+'').replace(/[^0-9\.]/g,''); const filePath = path.join(this.path, `${version}.html`); fs.mkdirSync(this.path, { recursive: true }); fs.writeFileSync(filePath, indexHtml); From 252ed8f0b178a24e414aed06555c09acf0cc42b3 Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Wed, 15 May 2024 14:11:11 -0300 Subject: [PATCH 24/47] make ESList god happy --- example.js | 8 ++++---- src/webCache/LocalWebCache.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example.js b/example.js index f6224ff50a..61f9077074 100644 --- a/example.js +++ b/example.js @@ -15,11 +15,11 @@ client.initialize(); client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); -client.pupPage.on("pageerror", function(err) { - console.log("Page error: " + err.toString()); +client.pupPage.on('pageerror', function(err) { + console.log('Page error: ' + err.toString()); }); -client.pupPage.on("error", function(err) { - console.log("Page error: " + err.toString()); +client.pupPage.on('error', function(err) { + console.log('Page error: ' + err.toString()); }); // Pairing code only needs to be requested once diff --git a/src/webCache/LocalWebCache.js b/src/webCache/LocalWebCache.js index ec6440f7be..4e86b5c67d 100644 --- a/src/webCache/LocalWebCache.js +++ b/src/webCache/LocalWebCache.js @@ -30,7 +30,7 @@ class LocalWebCache extends WebCache { } async persist(indexHtml, version) { - version = (version+'').replace(/[^0-9\.]/g,''); + version = (version+'').replace(/[^0-9.]/g,''); const filePath = path.join(this.path, `${version}.html`); fs.mkdirSync(this.path, { recursive: true }); fs.writeFileSync(filePath, indexHtml); From 2798396c1ec1342b6533969025e1a3faaca2e1f5 Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 15 May 2024 19:45:52 +0200 Subject: [PATCH 25/47] fix getInviteCode for group (#3007) (#3029) * fix getInviteCode for group (#3007) * move changes to proper getInviteCode method. * style fix * refactor: add try-catch in a case the user is not a group admin * refactor: handle promise properly --------- Co-authored-by: Sven Neumann Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com> --- src/structures/GroupChat.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 12448238da..2c165e5b6e 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -359,10 +359,18 @@ class GroupChat extends Chat { async getInviteCode() { const codeRes = await this.client.pupPage.evaluate(async chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.GroupInvite.queryGroupInviteCode(chatWid); + try { + return window.WWebJS.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') + ? await window.Store.GroupInvite.queryGroupInviteCode(chatWid, true) + : await window.Store.GroupInvite.queryGroupInviteCode(chatWid); + } + catch (err) { + if(err.name === 'ServerStatusCodeError') return undefined; + throw err; + } }, this.id._serialized); - return codeRes.code; + return codeRes?.code; } /** From 6df4eefeb2bf6ace61135b3cf9c51ee75d0e53d2 Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Thu, 16 May 2024 11:39:04 -0300 Subject: [PATCH 26/47] fixes --- example.js | 13 +++++++------ src/Client.js | 7 +++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/example.js b/example.js index 61f9077074..30e18203ac 100644 --- a/example.js +++ b/example.js @@ -15,12 +15,6 @@ client.initialize(); client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); -client.pupPage.on('pageerror', function(err) { - console.log('Page error: ' + err.toString()); -}); -client.pupPage.on('error', function(err) { - console.log('Page error: ' + err.toString()); -}); // Pairing code only needs to be requested once let pairingCodeRequested = false; @@ -48,6 +42,13 @@ client.on('auth_failure', msg => { client.on('ready', () => { console.log('READY'); + client.pupPage.on('pageerror', function(err) { + console.log('Page error: ' + err.toString()); + }); + client.pupPage.on('error', function(err) { + console.log('Page error: ' + err.toString()); + }); + }); client.on('message', async msg => { diff --git a/src/Client.js b/src/Client.js index d2e3e03b9c..aab16f7942 100644 --- a/src/Client.js +++ b/src/Client.js @@ -240,9 +240,12 @@ class Client extends EventEmitter { this.emit(Events.READY); this.authStrategy.afterAuthReady(); }); - + let lastPercent = null; await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => { - this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now + if (lastPercent !== percent) { + lastPercent = percent; + this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now + } }); } const logoutCatchInjected = await this.pupPage.evaluate(() => { From 1ecdc42e7e87721af798aa13b7a8ad952552320d Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Tue, 21 May 2024 17:18:16 -0300 Subject: [PATCH 27/47] Update LocalWebCache.js --- src/webCache/LocalWebCache.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webCache/LocalWebCache.js b/src/webCache/LocalWebCache.js index 4e86b5c67d..9c429683f8 100644 --- a/src/webCache/LocalWebCache.js +++ b/src/webCache/LocalWebCache.js @@ -30,11 +30,11 @@ class LocalWebCache extends WebCache { } async persist(indexHtml, version) { - version = (version+'').replace(/[^0-9.]/g,''); + // version = (version+'').replace(/[^0-9.]/g,''); const filePath = path.join(this.path, `${version}.html`); fs.mkdirSync(this.path, { recursive: true }); fs.writeFileSync(filePath, indexHtml); } } -module.exports = LocalWebCache; \ No newline at end of file +module.exports = LocalWebCache; From 96b4742686415ca124939685497f6076cd5d6a68 Mon Sep 17 00:00:00 2001 From: Jorge Rocha Gualtieri Date: Fri, 24 May 2024 18:39:38 -0300 Subject: [PATCH 28/47] fix delete on 2.3000 (#3048) * fix delete on 2.3000 * fix compareWwebVersions * fix: use proper comparison operator --------- Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com> --- src/structures/Message.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 6d3a8bde1f..af7a3bcff4 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -510,7 +510,11 @@ class Message extends Base { const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg); if (everyone && canRevoke) { - return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); + if (window.WWebJS.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')) { + return window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: true }); + } else { + return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); + } } return window.Store.Cmd.sendDeleteMsgs(chat, [msg], true); From bbb8d18274ab887c20f1778a9b17706cbbeac472 Mon Sep 17 00:00:00 2001 From: Mateus Mercer Date: Sat, 25 May 2024 12:05:14 -0300 Subject: [PATCH 29/47] Implement bot invoking capabilities (#3009) * Implement bot invoking capabilities * Update index.d.ts * Eslint --- index.d.ts | 2 ++ src/Client.js | 2 ++ src/util/Injected/Store.js | 2 ++ src/util/Injected/Utils.js | 15 +++++++++++++++ 4 files changed, 21 insertions(+) diff --git a/index.d.ts b/index.d.ts index 95eaf9cff3..c53a972936 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1073,6 +1073,8 @@ declare namespace WAWebJS { }[] /** Send 'seen' status */ sendSeen?: boolean + /** Bot Wid when doing a bot mention like @Meta AI */ + invokedBotWid?: string /** Media to be sent */ media?: MessageMedia /** Extra options */ diff --git a/src/Client.js b/src/Client.js index aab16f7942..8e27416059 100644 --- a/src/Client.js +++ b/src/Client.js @@ -829,6 +829,7 @@ class Client extends EventEmitter { * @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions * @property {string[]} [mentions] - User IDs to mention in the message * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message + * @property {string} [invokedBotWid=undefined] - Bot Wid when doing a bot mention like @Meta AI * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true). * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true). * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null. @@ -865,6 +866,7 @@ class Client extends EventEmitter { parseVCards: options.parseVCards === false ? false : true, mentionedJidList: options.mentions || [], groupMentions: options.groupMentions, + invokedBotWid: options.invokedBotWid, extraOptions: options.extra }; diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index 2e0c2201e3..8222664616 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -53,6 +53,8 @@ exports.ExposeStore = () => { window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists; window.Store.ReplyUtils = window.require('WAWebMsgReply'); window.Store.Settings = window.require('WAWebUserPrefsGeneral'); + window.Store.BotSecret = window.require('WAWebBotMessageSecret'); + window.Store.BotProfiles = window.require('WAWebBotProfileCollection'); window.Store.ForwardUtils = { ...window.require('WAWebForwardMessagesToChat') diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index 16f91d38c5..b5e90a3f5e 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -195,6 +195,15 @@ exports.LoadUtils = () => { delete listOptions.list.footer; } + const botOptions = {}; + if (options.invokedBotWid) { + botOptions.messageSecret = window.crypto.getRandomValues(new Uint8Array(32)); + botOptions.botMessageSecret = await window.Store.BotSecret.genBotMsgSecretFromMsgSecret(botOptions.messageSecret); + botOptions.invokedBotWid = window.Store.WidFactory.createWid(options.invokedBotWid); + botOptions.botPersonaId = window.Store.BotProfiles.BotProfileCollection.get(options.invokedBotWid).personaId; + delete options.invokedBotWid; + } + const meUser = window.Store.User.getMaybeMeUser(); const newId = await window.Store.MsgKey.newId(); @@ -232,8 +241,14 @@ exports.LoadUtils = () => { ...vcardOptions, ...buttonOptions, ...listOptions, + ...botOptions, ...extraOptions }; + + // Bot's won't reply if canonicalUrl is set (linking) + if (botOptions) { + delete message.canonicalUrl; + } await window.Store.SendMessage.addAndSendMsgToChat(chat, message); return window.Store.Msg.get(newMsgId._serialized); From 752d9ae6fd7730ab0631ea2edafb15635134ae00 Mon Sep 17 00:00:00 2001 From: Jorge Rocha Gualtieri Date: Wed, 12 Jun 2024 19:49:43 -0300 Subject: [PATCH 30/47] Webpack exodus fix message_reaction event (#3099) * exposed WAWebAddonReactionTableMode * capture reaction using WAWebAddonReactionTableMode * move compareWwebVersions and check version before trying to expose new store --- src/Client.js | 22 ++++++++++++++--- src/structures/GroupChat.js | 4 ++-- src/structures/Message.js | 2 +- src/util/Injected/Store.js | 45 +++++++++++++++++++++++++++++++++++ src/util/Injected/Utils.js | 47 +++---------------------------------- 5 files changed, 70 insertions(+), 50 deletions(-) diff --git a/src/Client.js b/src/Client.js index 8e27416059..e441ba5bd5 100644 --- a/src/Client.js +++ b/src/Client.js @@ -709,7 +709,23 @@ class Client extends EventEmitter { } }); 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 senderUserJid = reaction.author._serialized; + + return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp }; + })); + + return ogMethod(...args); + }).bind(module); + } else { const module = window.Store.createOrUpdateReactionsModule; const ogMethod = module.createOrUpdateReactions; module.createOrUpdateReactions = ((...args) => { @@ -1214,7 +1230,7 @@ class Client extends EventEmitter { const profilePic = await this.pupPage.evaluate(async contactId => { try { const chatWid = window.Store.WidFactory.createWid(contactId); - return window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0') + return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0') ? await window.Store.ProfilePic.profilePicFind(chatWid) : await window.Store.ProfilePic.requestProfilePicFromServer(chatWid); } catch (err) { @@ -1407,7 +1423,7 @@ class Client extends EventEmitter { comment, await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid) ); - isInviteV4Sent = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') + isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') ? addParticipantResult === 'OK' : addParticipantResult.messageSendResult === 'OK'; } diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 2c165e5b6e..f345a3beba 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -166,7 +166,7 @@ class GroupChat extends Chat { comment, await window.WWebJS.getProfilePicThumbToBase64(groupWid) ); - isInviteV4Sent = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') + isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') ? res === 'OK' : res.messageSendResult === 'OK'; } @@ -360,7 +360,7 @@ class GroupChat extends Chat { const codeRes = await this.client.pupPage.evaluate(async chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); try { - return window.WWebJS.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') + return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') ? await window.Store.GroupInvite.queryGroupInviteCode(chatWid, true) : await window.Store.GroupInvite.queryGroupInviteCode(chatWid); } diff --git a/src/structures/Message.js b/src/structures/Message.js index af7a3bcff4..1ee74bc9f2 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -510,7 +510,7 @@ class Message extends Base { const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg); if (everyone && canRevoke) { - if (window.WWebJS.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')) { + if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')) { return window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: true }); } else { return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); diff --git a/src/util/Injected/Store.js b/src/util/Injected/Store.js index 8222664616..873f09c2ab 100644 --- a/src/util/Injected/Store.js +++ b/src/util/Injected/Store.js @@ -1,6 +1,48 @@ 'use strict'; exports.ExposeStore = () => { + /** + * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version. + * @param {string} lOperand The left operand for the WWeb version string to compare with + * @param {string} operator The comparison operator + * @param {string} rOperand The right operand for the WWeb version string to compare with + * @returns {boolean} Boolean value that indicates the result of the comparison + */ + window.compareWwebVersions = (lOperand, operator, rOperand) => { + if (!['>', '>=', '<', '<=', '='].includes(operator)) { + throw new class _ extends Error { + constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } + }('Invalid comparison operator is provided'); + + } + if (typeof lOperand !== 'string' || typeof rOperand !== 'string') { + throw new class _ extends Error { + constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } + }('A non-string WWeb version type is provided'); + } + + lOperand = lOperand.replace(/-beta$/, ''); + rOperand = rOperand.replace(/-beta$/, ''); + + while (lOperand.length !== rOperand.length) { + lOperand.length > rOperand.length + ? rOperand = rOperand.concat('0') + : lOperand = lOperand.concat('0'); + } + + lOperand = Number(lOperand.replace(/\./g, '')); + rOperand = Number(rOperand.replace(/\./g, '')); + + return ( + operator === '>' ? lOperand > rOperand : + operator === '>=' ? lOperand >= rOperand : + operator === '<' ? lOperand < rOperand : + operator === '<=' ? lOperand <= rOperand : + operator === '=' ? lOperand === rOperand : + false + ); + }; + window.Store = Object.assign({}, window.require('WAWebCollections')); window.Store.AppState = window.require('WAWebSocketModel').Socket; window.Store.BlockContact = window.require('WAWebBlockContactAction'); @@ -55,6 +97,8 @@ exports.ExposeStore = () => { window.Store.Settings = window.require('WAWebUserPrefsGeneral'); window.Store.BotSecret = window.require('WAWebBotMessageSecret'); window.Store.BotProfiles = window.require('WAWebBotProfileCollection'); + if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) + window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode; window.Store.ForwardUtils = { ...window.require('WAWebForwardMessagesToChat') @@ -118,4 +162,5 @@ exports.ExposeStore = () => { window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); + }; diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index b5e90a3f5e..9399fbb7a7 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -7,7 +7,7 @@ exports.LoadUtils = () => { let msg = window.Store.Msg.get(msgId); let chat = window.Store.Chat.get(chatId); - if (window.WWebJS.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) { + if (window.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) { return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], false); } else { return chat.forwardMessages([msg]); @@ -485,7 +485,7 @@ exports.LoadUtils = () => { // TODO: remove useOldImplementation and its checks once all clients are updated to >= v2.2327.4 const useOldImplementation - = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.2327.4'); + = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2327.4'); res.isMe = useOldImplementation ? contact.isMe @@ -854,7 +854,7 @@ exports.LoadUtils = () => { }]; let rpcResult, resultArgs; - const isOldImpl = window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<=', '2.2335.9'); + const isOldImpl = window.compareWwebVersions(window.Debug.VERSION, '<=', '2.2335.9'); const data = { name: undefined, code: undefined, @@ -999,45 +999,4 @@ exports.LoadUtils = () => { return false; }; - /** - * 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'); - } - - 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 - ); - }; }; From efc2bd0c059e7132b062db8242bebdd16c3dba51 Mon Sep 17 00:00:00 2001 From: Jorge Rocha Gualtieri Date: Thu, 13 Jun 2024 10:22:46 -0300 Subject: [PATCH 31/47] message_reaction on example.js (#3102) * handle message_reaction event on example.js * log wweb version on ready --- example.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/example.js b/example.js index 30e18203ac..bc93dd5d86 100644 --- a/example.js +++ b/example.js @@ -40,8 +40,11 @@ client.on('auth_failure', msg => { console.error('AUTHENTICATION FAILURE', msg); }); -client.on('ready', () => { +client.on('ready', async () => { console.log('READY'); + const debugWWebVersion = await client.getWWebVersion(); + console.log(`WWebVersion = ${debugWWebVersion}`); + client.pupPage.on('pageerror', function(err) { console.log('Page error: ' + err.toString()); }); @@ -611,4 +614,8 @@ client.on('group_membership_request', async (notification) => { /** You can approve or reject the newly appeared membership request: */ await client.approveGroupMembershipRequestss(notification.chatId, notification.author); await client.rejectGroupMembershipRequests(notification.chatId, notification.author); +}); + +client.on('message_reaction', async (reaction) => { + console.log('REACTION RECEIVED', reaction); }); \ No newline at end of file From cd566f28b9957d58dbd470470f6e2fe018808db3 Mon Sep 17 00:00:00 2001 From: Jorge Rocha Gualtieri Date: Fri, 14 Jun 2024 11:24:09 -0300 Subject: [PATCH 32/47] fix reactions on pvt chats (#3104) --- src/Client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Client.js b/src/Client.js index e441ba5bd5..8bf4e59c01 100644 --- a/src/Client.js +++ b/src/Client.js @@ -718,7 +718,8 @@ class Client extends EventEmitter { const msgKey = reaction.id; const parentMsgKey = reaction.reactionParentKey; const timestamp = reaction.reactionTimestamp / 1000; - const senderUserJid = reaction.author._serialized; + const sender = reaction.author ?? reaction.from; + const senderUserJid = sender._serialized; return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp }; })); From 4ddbb0d7bb30ff26f233d9cc48569c0c09041b85 Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:23:48 -0300 Subject: [PATCH 33/47] New group function --- src/Client.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Client.js b/src/Client.js index 8bf4e59c01..914810886e 100644 --- a/src/Client.js +++ b/src/Client.js @@ -1399,10 +1399,12 @@ class Client extends EventEmitter { try { createGroupResult = await window.Store.GroupUtils.createGroup( - title, - participantWids, - messageTimer, - parentGroupWid + { + 'title':title, + 'messageTimer':messageTimer, + 'parentGroupWid':parentGroupWid, + }, + participantWids ); } catch (err) { return 'CreateGroupError: An unknown error occupied while creating a group'; From 8034e8f5ad7411664eb22173fc68075d2820854e Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:32:46 -0300 Subject: [PATCH 34/47] new group settings --- src/Client.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Client.js b/src/Client.js index 914810886e..8e18355242 100644 --- a/src/Client.js +++ b/src/Client.js @@ -1400,9 +1400,15 @@ class Client extends EventEmitter { try { createGroupResult = await window.Store.GroupUtils.createGroup( { - 'title':title, - 'messageTimer':messageTimer, - 'parentGroupWid':parentGroupWid, + 'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode, + 'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode, + 'announce': options.announce === undefined ? true : options.announce, + 'ephemeralDuration': messageTimer, + 'full': undefined, + 'parentGroupId': parentGroupWid, + 'restrict': options.restrict === undefined ? true : options.restrict, + 'thumb': undefined, + 'title': title, }, participantWids ); From f361cde7d4f50dcb0694b7d5bb571d16b7bf412f Mon Sep 17 00:00:00 2001 From: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:10:53 -0300 Subject: [PATCH 35/47] Update Client.js --- src/Client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.js b/src/Client.js index e46724f6b3..4825638f13 100644 --- a/src/Client.js +++ b/src/Client.js @@ -686,7 +686,7 @@ class Client extends EventEmitter { }); } - await page.exposeFunction('onPollVoteEvent', (vote) => { + await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => { const _vote = new PollVote(this, vote); /** * Emitted when some poll option is selected or deselected, From 4494fa0445415cffb0151b00ede3b5f380fdd85d Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Wed, 3 Jul 2024 02:03:52 +0800 Subject: [PATCH 36/47] Switch pairing method between QR and phone number --- .gitignore | 3 ++ src/Client.js | 98 +++++++++++++++++++++++++++---------------- src/util/Constants.js | 8 +++- 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 12b53d4ca5..296fc21274 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,6 @@ typings/ # local version cache .wwebjs_cache/ + +# VS Code configurations +.vscode \ No newline at end of file diff --git a/src/Client.js b/src/Client.js index 4825638f13..fe8fe265bb 100644 --- a/src/Client.js +++ b/src/Client.js @@ -140,42 +140,58 @@ class Client extends EventEmitter { return; } - // Register qr events + // Register qr/code events let qrRetries = 0; const injected = await this.pupPage.evaluate(() => { - return typeof window.onQRChangedEvent !== 'undefined'; + return typeof window.onQRChangedEvent !== 'undefined' || typeof window.onCodeReceivedEvent !== 'undefined'; }); - if (!injected) { - await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => { - /** - * Emitted when a QR code is received - * @event Client#qr - * @param {string} qr QR Code - */ - this.emit(Events.QR_RECEIVED, qr); - if (this.options.qrMaxRetries > 0) { - qrRetries++; - if (qrRetries > this.options.qrMaxRetries) { - this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); - await this.destroy(); + if(this.options.pairWithPhoneNumber.phoneNumber){ + if (!injected) { + await this.pupPage.exposeFunction('onCodeReceivedEvent', async (code) => { + /** + * Emitted when a pairing code is received + * @event Client#code + * @param {string} code Code + */ + this.emit(Events.CODE_RECEIVED, code); + }); + } + const pairWithPhoneNumber = this.options.pairWithPhoneNumber; + const code = await this.requestPairingCode(pairWithPhoneNumber.phoneNumber,pairWithPhoneNumber.showNotification,pairWithPhoneNumber.intervalMs); + this.emit(Events.CODE_RECEIVED, code); // initial code + } else { + if(!injected){ + await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => { + /** + * Emitted when a QR code is received + * @event Client#qr + * @param {string} qr QR Code + */ + this.emit(Events.QR_RECEIVED, qr); + if (this.options.qrMaxRetries > 0) { + qrRetries++; + if (qrRetries > this.options.qrMaxRetries) { + this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); + await this.destroy(); + } } - } - }); - } + }); + } - await this.pupPage.evaluate(async () => { - const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); - const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get(); - const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey); - const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey); - const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey(); - const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; - const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform; - - window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr - window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes - }); + await this.pupPage.evaluate(async () => { + const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); + const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get(); + const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey); + const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey); + const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey(); + const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; + const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform; + + window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr + window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes + }); + } } if (!reinject) { @@ -358,12 +374,22 @@ class Client extends EventEmitter { * @param {boolean} showNotification - Show notification to pair on phone number * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ - async requestPairingCode(phoneNumber, showNotification = true) { - return await this.pupPage.evaluate(async (phoneNumber, showNotification) => { - window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); - await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); - return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); - }, phoneNumber, showNotification); + async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) { + return await this.pupPage.evaluate(async (phoneNumber, showNotification, intervalMs) => { + const getCode = async () => { + window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); + await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); + return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); + } + const interval = setInterval(async () => { + if(window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE'){ + clearInterval(interval); + return; + } + window.onCodeReceivedEvent(await getCode()); + }, intervalMs); + return getCode(); + }, phoneNumber, showNotification, intervalMs); } /** diff --git a/src/util/Constants.js b/src/util/Constants.js index e8c5fe3e08..dc67d4ac57 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -18,7 +18,12 @@ exports.DefaultOptions = { 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 + proxyAuthentication: undefined, + pairWithPhoneNumber: { + phoneNumber: "", + showNotification: true, + intervalMs: 180000, + }, }; /** @@ -60,6 +65,7 @@ exports.Events = { GROUP_MEMBERSHIP_REQUEST: 'group_membership_request', GROUP_UPDATE: 'group_update', QR_RECEIVED: 'qr', + CODE_RECEIVED: 'code', LOADING_SCREEN: 'loading_screen', DISCONNECTED: 'disconnected', STATE_CHANGED: 'change_state', From cac19f2eaf6f47da3423d72c7d5ad80516510284 Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Wed, 3 Jul 2024 09:51:46 +0800 Subject: [PATCH 37/47] Remove existing pairing code interval --- src/Client.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Client.js b/src/Client.js index fe8fe265bb..cf7b8db2b7 100644 --- a/src/Client.js +++ b/src/Client.js @@ -381,12 +381,15 @@ class Client extends EventEmitter { await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); } - const interval = setInterval(async () => { - if(window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE'){ - clearInterval(interval); + if (window.codeInterval) { + clearInterval(window.codeInterval) // remove existing interval + } + window.codeInterval = setInterval(async () => { + if (window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE') { + clearInterval(window.codeInterval); return; } - window.onCodeReceivedEvent(await getCode()); + window.onCodeReceivedEvent(await getCode()); }, intervalMs); return getCode(); }, phoneNumber, showNotification, intervalMs); From 113b2a997d30b0c1ce34f24a0bb41a411a38a249 Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Wed, 3 Jul 2024 11:41:20 +0800 Subject: [PATCH 38/47] Modify example.js --- example.js | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/example.js b/example.js index 74aaf012e5..e9b1280833 100644 --- a/example.js +++ b/example.js @@ -6,7 +6,10 @@ const client = new Client({ puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], headless: false, - } + }, + // pairWithPhoneNumber: { + // phoneNumber: "96170100100" // Pair with phone number (format: ) + // } }); // client initialize does not finish at ready now. @@ -16,19 +19,8 @@ client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); -// Pairing code only needs to be requested once -let pairingCodeRequested = false; -client.on('qr', async (qr) => { - // NOTE: This event will not be fired if a session is specified. - console.log('QR RECEIVED', qr); - - // paiuting code example - const pairingCodeEnabled = false; - if (pairingCodeEnabled && !pairingCodeRequested) { - const pairingCode = await client.requestPairingCode('96170100100'); // enter the target phone number - console.log('Pairing code enabled, code: '+ pairingCode); - pairingCodeRequested = true; - } +client.on('code', (code) => { + console.log("Pairing code:",code); }); client.on('authenticated', () => { From d14f094ab198c7878ba57e98adecca9e89248ca0 Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Wed, 3 Jul 2024 14:46:01 +0800 Subject: [PATCH 39/47] Update docs --- index.d.ts | 19 ++++++++++++++++++- src/Client.js | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 93b4a4e836..0e4727bd2b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -119,9 +119,10 @@ declare namespace WAWebJS { * Request authentication via pairing code instead of QR code * @param phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) * @param showNotification - Show notification to pair on phone number + * @param intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ - requestPairingCode(phoneNumber: string, showNotification = true): Promise + requestPairingCode(phoneNumber: string, showNotification = true, intervalMs = 180000): Promise /** Force reset of connection state for the client */ resetState(): Promise @@ -372,6 +373,13 @@ declare namespace WAWebJS { qr: string ) => void): this + /** Emitted when the phone number pairing code is received */ + on(event: 'code', listener: ( + /** pairing code string + * @example `8W2WZ3TS` */ + code: string + ) => void): this + /** Emitted when a call is received */ on(event: 'call', listener: ( /** The call that started */ @@ -471,6 +479,15 @@ declare namespace WAWebJS { ffmpegPath?: string, /** Object with proxy autentication requirements @default: undefined */ proxyAuthentication?: {username: string, password: string} | undefined + /** Phone number pairing configuration. Refer the requestPairingCode function of Client. + * @default + * { + * phoneNumber: "", + * showNotification: true, + * intervalMs: 180000, + * } + */ + pairWithPhoneNumber?: {phoneNumber: string, showNotification?: boolean, intervalMs?: number} } export interface LocalWebCacheOptions { diff --git a/src/Client.js b/src/Client.js index cf7b8db2b7..5ed1a80cf5 100644 --- a/src/Client.js +++ b/src/Client.js @@ -372,6 +372,7 @@ class Client extends EventEmitter { * Request authentication via pairing code instead of QR code * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) * @param {boolean} showNotification - Show notification to pair on phone number + * @param {number} intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) { From adbfb3df5629b183025a2b154852fae3490c66f3 Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Wed, 3 Jul 2024 21:03:37 +0800 Subject: [PATCH 40/47] Small improvements in inject function --- src/Client.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Client.js b/src/Client.js index 5ed1a80cf5..33c23d2c90 100644 --- a/src/Client.js +++ b/src/Client.js @@ -94,7 +94,7 @@ class Client extends EventEmitter { */ async inject(reinject = false) { await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs}); - + const pairWithPhoneNumber = this.options.pairWithPhoneNumber; const version = await this.getWWebVersion(); const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000; @@ -141,26 +141,26 @@ class Client extends EventEmitter { } // Register qr/code events - let qrRetries = 0; const injected = await this.pupPage.evaluate(() => { return typeof window.onQRChangedEvent !== 'undefined' || typeof window.onCodeReceivedEvent !== 'undefined'; }); - if(this.options.pairWithPhoneNumber.phoneNumber){ + if(pairWithPhoneNumber.phoneNumber){ if (!injected) { await this.pupPage.exposeFunction('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; }); } - const pairWithPhoneNumber = this.options.pairWithPhoneNumber; - const code = await this.requestPairingCode(pairWithPhoneNumber.phoneNumber,pairWithPhoneNumber.showNotification,pairWithPhoneNumber.intervalMs); - this.emit(Events.CODE_RECEIVED, code); // initial code + this.requestPairingCode(pairWithPhoneNumber.phoneNumber,pairWithPhoneNumber.showNotification,pairWithPhoneNumber.intervalMs); } else { if(!injected){ + let qrRetries = 0; await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => { /** * Emitted when a QR code is received @@ -196,7 +196,7 @@ class Client extends EventEmitter { if (!reinject) { await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => { - if (state == 'UNPAIRED_IDLE') { + if (state == 'UNPAIRED_IDLE' && !pairWithPhoneNumber.phoneNumber) { // refresh qr code window.Store.Cmd.refreshQR(); } @@ -383,7 +383,7 @@ class Client extends EventEmitter { return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); } if (window.codeInterval) { - clearInterval(window.codeInterval) // remove existing interval + clearInterval(window.codeInterval); // remove existing interval } window.codeInterval = setInterval(async () => { if (window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE') { @@ -392,7 +392,7 @@ class Client extends EventEmitter { } window.onCodeReceivedEvent(await getCode()); }, intervalMs); - return getCode(); + return window.onCodeReceivedEvent(await getCode()); }, phoneNumber, showNotification, intervalMs); } From b5c64b8ece03bb27a67dd38dc9bf38be6b1b524e Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Tue, 1 Oct 2024 18:28:20 +0800 Subject: [PATCH 41/47] Fix ESLint --- example.js | 4 ++-- src/Client.js | 2 +- src/util/Constants.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example.js b/example.js index 69b4fa74b3..4abdd363b8 100644 --- a/example.js +++ b/example.js @@ -8,7 +8,7 @@ const client = new Client({ headless: false, }, // pairWithPhoneNumber: { - // phoneNumber: "96170100100" // Pair with phone number (format: ) + // phoneNumber: '96170100100' // Pair with phone number (format: ) // } }); @@ -20,7 +20,7 @@ client.on('loading_screen', (percent, message) => { }); client.on('code', (code) => { - console.log("Pairing code:",code); + console.log('Pairing code:',code); }); client.on('authenticated', () => { diff --git a/src/Client.js b/src/Client.js index 2019557df6..ae42a9fc3c 100644 --- a/src/Client.js +++ b/src/Client.js @@ -367,7 +367,7 @@ class Client extends EventEmitter { 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 } diff --git a/src/util/Constants.js b/src/util/Constants.js index 3104e8779f..9091651155 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -20,7 +20,7 @@ exports.DefaultOptions = { bypassCSP: false, proxyAuthentication: undefined, pairWithPhoneNumber: { - phoneNumber: "", + phoneNumber: '', showNotification: true, intervalMs: 180000, }, From cd890cdfaee6e56636b8c5553f163156eb64f9d0 Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Tue, 1 Oct 2024 20:35:32 +0800 Subject: [PATCH 42/47] example.js correction --- example.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/example.js b/example.js index 4abdd363b8..402ce5140b 100644 --- a/example.js +++ b/example.js @@ -19,6 +19,11 @@ client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); +client.on('qr', async (qr) => { + // NOTE: This event will not be fired if a session is specified. + console.log('QR RECEIVED', qr); +}); + client.on('code', (code) => { console.log('Pairing code:',code); }); From 723b211b9d25de2be823e9e83f2c400463cca9ce Mon Sep 17 00:00:00 2001 From: alechkos <93551621+alechkos@users.noreply.github.com> Date: Fri, 8 Nov 2024 21:39:26 +0200 Subject: [PATCH 43/47] docs: fix docs for 'requestPairingCode' method Co-authored-by: Leandro Matilla <30844484+wis-dev@users.noreply.github.com> --- index.d.ts | 6 +++--- src/Client.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index dd7f2e3a60..1eae481af4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -121,11 +121,11 @@ declare namespace WAWebJS { /** * Request authentication via pairing code instead of QR code * @param phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) - * @param showNotification - Show notification to pair on phone number - * @param intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) + * @param showNotification - Show notification to pair on phone number. Defaults to `true` + * @param intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes). Defaults to `180000` * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ - requestPairingCode(phoneNumber: string, showNotification = true, intervalMs = 180000): Promise + requestPairingCode(phoneNumber: string, showNotification?: boolean, intervalMs?: number): Promise /** Force reset of connection state for the client */ resetState(): Promise diff --git a/src/Client.js b/src/Client.js index ae42a9fc3c..35b9540702 100644 --- a/src/Client.js +++ b/src/Client.js @@ -357,8 +357,8 @@ class Client extends EventEmitter { /** * Request authentication via pairing code instead of QR code * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) - * @param {boolean} showNotification - Show notification to pair on phone number - * @param {number} intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) + * @param {boolean} [showNotification = true] - Show notification to pair on phone number + * @param {number} [intervalMs = 180000] - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) { From 05fa0753be0a871688375866df5dfd7d18fa15f0 Mon Sep 17 00:00:00 2001 From: BenyFilho <168232825+BenyFilho@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:26:03 -0300 Subject: [PATCH 44/47] Update Utils.js getChatModel because Meta chat object affected Group Chats --- src/util/Injected/Utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index 6052627fde..b5529226d7 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -451,11 +451,11 @@ exports.LoadUtils = () => { window.WWebJS.getChatModel = async chat => { let res = chat.serialize(); - res.isGroup = chat.isGroup; res.formattedTitle = chat.formattedTitle; - res.isMuted = chat.mute && chat.mute.isMuted; + res.isMuted = chat.muteExpiration == 0 ? false : true; 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(); From 538cb5f7cd9963c705d2c3aefce6d01b190f2e01 Mon Sep 17 00:00:00 2001 From: BenyFilho <168232825+BenyFilho@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:18:47 -0300 Subject: [PATCH 45/47] Ajust to set isGroup as false as default --- src/util/Injected/Utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index b5529226d7..f47644154b 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -451,6 +451,7 @@ exports.LoadUtils = () => { window.WWebJS.getChatModel = async chat => { let res = chat.serialize(); + res.isGroup = false; res.formattedTitle = chat.formattedTitle; res.isMuted = chat.muteExpiration == 0 ? false : true; From c5d5e5626684742cadbf58f28739c1c2c9dcc582 Mon Sep 17 00:00:00 2001 From: Mob Code 100 <66469454+MobCode100@users.noreply.github.com> Date: Sat, 7 Dec 2024 17:13:03 +0800 Subject: [PATCH 46/47] Remove the need for custom interval --- index.d.ts | 6 ++---- src/Client.js | 34 ++++++++++++++++++++++++++-------- src/util/Constants.js | 1 - 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/index.d.ts b/index.d.ts index 1eae481af4..8f72069eb6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -122,10 +122,9 @@ declare namespace WAWebJS { * Request authentication via pairing code instead of QR code * @param phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) * @param showNotification - Show notification to pair on phone number. Defaults to `true` - * @param intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes). Defaults to `180000` * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ - requestPairingCode(phoneNumber: string, showNotification?: boolean, intervalMs?: number): Promise + requestPairingCode(phoneNumber: string, showNotification?: boolean): Promise /** Force reset of connection state for the client */ resetState(): Promise @@ -498,10 +497,9 @@ declare namespace WAWebJS { * { * phoneNumber: "", * showNotification: true, - * intervalMs: 180000, * } */ - pairWithPhoneNumber?: {phoneNumber: string, showNotification?: boolean, intervalMs?: number} + pairWithPhoneNumber?: {phoneNumber: string, showNotification?: boolean} } export interface LocalWebCacheOptions { diff --git a/src/Client.js b/src/Client.js index 35b9540702..865f22d05f 100644 --- a/src/Client.js +++ b/src/Client.js @@ -87,6 +87,11 @@ class Client extends EventEmitter { this.lastLoggedOut = false; Util.setFfmpegPath(this.options.ffmpegPath); + + /** + * @type {number} - The time in seconds before pairing code expires + */ + this.pairingCodeTTL = null; } /** * Injection logic @@ -152,7 +157,7 @@ class Client extends EventEmitter { this.emit(Events.CODE_RECEIVED, code); return code; }); - this.requestPairingCode(pairWithPhoneNumber.phoneNumber,pairWithPhoneNumber.showNotification,pairWithPhoneNumber.intervalMs); + this.requestPairingCode(pairWithPhoneNumber.phoneNumber,pairWithPhoneNumber.showNotification); } else { let qrRetries = 0; await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => { @@ -358,11 +363,10 @@ class Client extends EventEmitter { * Request authentication via pairing code instead of QR code * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) * @param {boolean} [showNotification = true] - Show notification to pair on phone number - * @param {number} [intervalMs = 180000] - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ - async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) { - return await this.pupPage.evaluate(async (phoneNumber, showNotification, intervalMs) => { + async requestPairingCode(phoneNumber, showNotification = true) { + return await this.pupPage.evaluate(async (phoneNumber, showNotification, pairingCodeTTL) => { const getCode = async () => { window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); @@ -377,9 +381,9 @@ class Client extends EventEmitter { return; } window.onCodeReceivedEvent(await getCode()); - }, intervalMs); + }, pairingCodeTTL * 1000); return window.onCodeReceivedEvent(await getCode()); - }, phoneNumber, showNotification, intervalMs); + }, phoneNumber, showNotification, this.pairingCodeTTL); } /** @@ -795,9 +799,23 @@ class Client extends EventEmitter { }); } else { this.pupPage.on('response', async (res) => { + const textContent = await res.text(); + // Get pairing code expiration time in seconds + if(this.PairingCodeTTL == null && this.options.pairWithPhoneNumber.phoneNumber){ + const index = textContent.indexOf('("WAWebAltDeviceLinkingApi",['); + if(index > -1){ + const execRegex = (reg) => { + reg.lastIndex = index; + return reg.exec(textContent); + } + const captureVarName = execRegex(/.codeGenerationTs>(.+?)\)/g); + // Find last occurrence of the variable definition + const captureValue = execRegex(new RegExp(`${captureVarName[1]}=(\\d+)(?!.*${captureVarName[1]}=.+?codeGenerationTs>)`,"g")); + this.PairingCodeTTL = Number(captureValue[1]); + } + } if(res.ok() && res.url() === WhatsWebURL) { - const indexHtml = await res.text(); - this.currentIndexHtml = indexHtml; + this.currentIndexHtml = textContent; } }); } diff --git a/src/util/Constants.js b/src/util/Constants.js index 8717daf957..7556336f6e 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -22,7 +22,6 @@ exports.DefaultOptions = { pairWithPhoneNumber: { phoneNumber: '', showNotification: true, - intervalMs: 180000, }, }; From acc0f50d7cb48df32693d52a129941184cbc0e9a Mon Sep 17 00:00:00 2001 From: Mob Code 100 Date: Thu, 12 Dec 2024 17:33:51 +0800 Subject: [PATCH 47/47] Fix typo --- src/Client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Client.js b/src/Client.js index 865f22d05f..fc44e306a8 100644 --- a/src/Client.js +++ b/src/Client.js @@ -800,8 +800,8 @@ class Client extends EventEmitter { } else { this.pupPage.on('response', async (res) => { const textContent = await res.text(); - // Get pairing code expiration time in seconds - if(this.PairingCodeTTL == null && this.options.pairWithPhoneNumber.phoneNumber){ + // Get pairing code expiration time in seconds + if(this.pairingCodeTTL == null && this.options.pairWithPhoneNumber.phoneNumber){ const index = textContent.indexOf('("WAWebAltDeviceLinkingApi",['); if(index > -1){ const execRegex = (reg) => { @@ -811,7 +811,7 @@ class Client extends EventEmitter { const captureVarName = execRegex(/.codeGenerationTs>(.+?)\)/g); // Find last occurrence of the variable definition const captureValue = execRegex(new RegExp(`${captureVarName[1]}=(\\d+)(?!.*${captureVarName[1]}=.+?codeGenerationTs>)`,"g")); - this.PairingCodeTTL = Number(captureValue[1]); + this.pairingCodeTTL = Number(captureValue[1]); } } if(res.ok() && res.url() === WhatsWebURL) {