Files
whatsapp-web.js/docs/Client.js.html
2023-01-15 18:00:25 -04:00

1237 lines
51 KiB
HTML

<!doctype html>
<html>
<head>
<meta name="generator" content="JSDoc 3.6.7">
<meta charset="utf-8">
<title>whatsapp-web.js 1.19.2 &raquo; Source: Client.js</title>
<link rel="stylesheet" href="https://brick.a.ssl.fastly.net/Karla:400,400i,700,700i" type="text/css">
<link rel="stylesheet" href="https://brick.a.ssl.fastly.net/Noto+Serif:400,400i,700,700i" type="text/css">
<link rel="stylesheet" href="https://brick.a.ssl.fastly.net/Inconsolata:500" type="text/css">
<link href="css/baseline.css" rel="stylesheet">
</head>
<body onload="prettyPrint()">
<nav id="jsdoc-navbar" role="navigation" class="jsdoc-navbar">
<div id="jsdoc-navbar-container">
<div id="jsdoc-navbar-content">
<a href="index.html" class="jsdoc-navbar-package-name">whatsapp-web.<wbr>js 1.<wbr>19.<wbr>2</a>
</div>
</div>
</nav>
<div id="jsdoc-body-container">
<div id="jsdoc-content">
<div id="jsdoc-content-container">
<div id="jsdoc-banner" role="banner">
</div>
<div id="jsdoc-main" role="main">
<header class="page-header">
<h1>Source: Client.js</h1>
</header>
<article>
<pre class="prettyprint linenums"><code>&#x27;use strict&#x27;;
const EventEmitter &#x3D; require(&#x27;events&#x27;);
const puppeteer &#x3D; require(&#x27;puppeteer&#x27;);
const moduleRaid &#x3D; require(&#x27;@pedroslopez/moduleraid/moduleraid&#x27;);
const Util &#x3D; require(&#x27;./util/Util&#x27;);
const InterfaceController &#x3D; require(&#x27;./util/InterfaceController&#x27;);
const { WhatsWebURL, DefaultOptions, Events, WAState } &#x3D; require(&#x27;./util/Constants&#x27;);
const { ExposeStore, LoadUtils } &#x3D; require(&#x27;./util/Injected&#x27;);
const ChatFactory &#x3D; require(&#x27;./factories/ChatFactory&#x27;);
const ContactFactory &#x3D; require(&#x27;./factories/ContactFactory&#x27;);
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List, Reaction } &#x3D; require(&#x27;./structures&#x27;);
const LegacySessionAuth &#x3D; require(&#x27;./authStrategies/LegacySessionAuth&#x27;);
const NoAuth &#x3D; require(&#x27;./authStrategies/NoAuth&#x27;);
/**
* Starting point for interacting with the WhatsApp Web API
* @extends {EventEmitter}
* @param {object} options - Client options
* @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used.
* @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
* @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
* @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up
* @param {string} options.restartOnAuthFail - @deprecated This option should be set directly on the LegacySessionAuth.
* @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
* @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
* @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
* @param {string} options.userAgent - User agent to use in puppeteer
* @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers
* @param {boolean} options.bypassCSP - Sets bypassing of page&#x27;s Content-Security-Policy.
*
* @fires Client#qr
* @fires Client#authenticated
* @fires Client#auth_failure
* @fires Client#ready
* @fires Client#message
* @fires Client#message_ack
* @fires Client#message_create
* @fires Client#message_revoke_me
* @fires Client#message_revoke_everyone
* @fires Client#media_uploaded
* @fires Client#group_join
* @fires Client#group_leave
* @fires Client#group_update
* @fires Client#disconnected
* @fires Client#change_state
*/
class Client extends EventEmitter {
constructor(options &#x3D; {}) {
super();
this.options &#x3D; Util.mergeDefault(DefaultOptions, options);
if(!this.options.authStrategy) {
if(Object.prototype.hasOwnProperty.call(this.options, &#x27;session&#x27;)) {
process.emitWarning(
&#x27;options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. &#x27; +
&#x27;Use the LocalAuth authStrategy, don\&#x27;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).&#x27;,
&#x27;DeprecationWarning&#x27;
);
this.authStrategy &#x3D; new LegacySessionAuth({
session: this.options.session,
restartOnAuthFail: this.options.restartOnAuthFail
});
} else {
this.authStrategy &#x3D; new NoAuth();
}
} else {
this.authStrategy &#x3D; this.options.authStrategy;
}
this.authStrategy.setup(this);
this.pupBrowser &#x3D; null;
this.pupPage &#x3D; null;
Util.setFfmpegPath(this.options.ffmpegPath);
}
/**
* Sets up events and requirements, kicks off authentication request
*/
async initialize() {
let [browser, page] &#x3D; [null, null];
await this.authStrategy.beforeBrowserInitialized();
const puppeteerOpts &#x3D; this.options.puppeteer;
if (puppeteerOpts &amp;amp;&amp;amp; puppeteerOpts.browserWSEndpoint) {
browser &#x3D; await puppeteer.connect(puppeteerOpts);
page &#x3D; await browser.newPage();
} else {
const browserArgs &#x3D; [...(puppeteerOpts.args || [])];
if(!browserArgs.find(arg &#x3D;&gt; arg.includes(&#x27;--user-agent&#x27;))) {
browserArgs.push(&#x60;--user-agent&#x3D;${this.options.userAgent}&#x60;);
}
browser &#x3D; await puppeteer.launch({...puppeteerOpts, args: browserArgs});
page &#x3D; (await browser.pages())[0];
}
await page.setUserAgent(this.options.userAgent);
if (this.options.bypassCSP) await page.setBypassCSP(true);
this.pupBrowser &#x3D; browser;
this.pupPage &#x3D; page;
await this.authStrategy.afterBrowserInitialized();
await page.goto(WhatsWebURL, {
waitUntil: &#x27;load&#x27;,
timeout: 0,
referer: &#x27;https://whatsapp.com/&#x27;
});
await page.evaluate(&#x60;function getElementByXpath(path) {
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}&#x60;);
let lastPercent &#x3D; null,
lastPercentMessage &#x3D; null;
await page.exposeFunction(&#x27;loadingScreen&#x27;, async (percent, message) &#x3D;&gt; {
if (lastPercent !&#x3D;&#x3D; percent || lastPercentMessage !&#x3D;&#x3D; message) {
this.emit(Events.LOADING_SCREEN, percent, message);
lastPercent &#x3D; percent;
lastPercentMessage &#x3D; message;
}
});
await page.evaluate(
async function (selectors) {
var observer &#x3D; new MutationObserver(function () {
let progressBar &#x3D; window.getElementByXpath(
selectors.PROGRESS
);
let progressMessage &#x3D; window.getElementByXpath(
selectors.PROGRESS_MESSAGE
);
if (progressBar) {
window.loadingScreen(
progressBar.value,
progressMessage.innerText
);
}
});
observer.observe(document, {
attributes: true,
childList: true,
characterData: true,
subtree: true,
});
},
{
PROGRESS: &#x27;//*[@id&#x3D;\&#x27;app\&#x27;]/div/div/div[2]/progress&#x27;,
PROGRESS_MESSAGE: &#x27;//*[@id&#x3D;\&#x27;app\&#x27;]/div/div/div[3]&#x27;,
}
);
const INTRO_IMG_SELECTOR &#x3D; &#x27;[data-testid&#x3D;&quot;intro-md-beta-logo-dark&quot;], [data-testid&#x3D;&quot;intro-md-beta-logo-light&quot;], [data-asset-intro-image-light&#x3D;&quot;true&quot;], [data-asset-intro-image-dark&#x3D;&quot;true&quot;]&#x27;;
const INTRO_QRCODE_SELECTOR &#x3D; &#x27;div[data-ref] canvas&#x27;;
// Checks which selector appears first
const needAuthentication &#x3D; await Promise.race([
new Promise(resolve &#x3D;&gt; {
page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs })
.then(() &#x3D;&gt; resolve(false))
.catch((err) &#x3D;&gt; resolve(err));
}),
new Promise(resolve &#x3D;&gt; {
page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs })
.then(() &#x3D;&gt; resolve(true))
.catch((err) &#x3D;&gt; 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 } &#x3D; await this.authStrategy.onAuthenticationNeeded();
if(failed) {
/**
* Emitted when there has been an error while trying to restore an existing session
* @event Client#auth_failure
* @param {string} message
*/
this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
await this.destroy();
if (restart) {
// session restore failed so try again but without session to force new authentication
return this.initialize();
}
return;
}
const QR_CONTAINER &#x3D; &#x27;div[data-ref]&#x27;;
const QR_RETRY_BUTTON &#x3D; &#x27;div[data-ref] &gt; span &gt; button&#x27;;
let qrRetries &#x3D; 0;
await page.exposeFunction(&#x27;qrChanged&#x27;, async (qr) &#x3D;&gt; {
/**
* 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 &gt; 0) {
qrRetries++;
if (qrRetries &gt; this.options.qrMaxRetries) {
this.emit(Events.DISCONNECTED, &#x27;Max qrcode retries reached&#x27;);
await this.destroy();
}
}
});
await page.evaluate(function (selectors) {
const qr_container &#x3D; document.querySelector(selectors.QR_CONTAINER);
window.qrChanged(qr_container.dataset.ref);
const obs &#x3D; new MutationObserver((muts) &#x3D;&gt; {
muts.forEach(mut &#x3D;&gt; {
// Listens to qr token change
if (mut.type &#x3D;&#x3D;&#x3D; &#x27;attributes&#x27; &amp;amp;&amp;amp; mut.attributeName &#x3D;&#x3D;&#x3D; &#x27;data-ref&#x27;) {
window.qrChanged(mut.target.dataset.ref);
} else
// Listens to retry button, when found, click it
if (mut.type &#x3D;&#x3D;&#x3D; &#x27;childList&#x27;) {
const retry_button &#x3D; document.querySelector(selectors.QR_RETRY_BUTTON);
if (retry_button) retry_button.click();
}
});
});
obs.observe(qr_container.parentElement, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: [&#x27;data-ref&#x27;],
});
}, {
QR_CONTAINER,
QR_RETRY_BUTTON
});
// Wait for code scan
try {
await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 });
} catch(error) {
if (
error.name &#x3D;&#x3D;&#x3D; &#x27;ProtocolError&#x27; &amp;amp;&amp;amp;
error.message &amp;amp;&amp;amp;
error.message.match(/Target closed/)
) {
// something has called .destroy() while waiting
return;
}
throw error;
}
}
await page.evaluate(ExposeStore, moduleRaid.toString());
const authEventPayload &#x3D; await this.authStrategy.getAuthEventPayload();
/**
* Emitted when authentication is successful
* @event Client#authenticated
*/
this.emit(Events.AUTHENTICATED, authEventPayload);
// Check window.Store Injection
await page.waitForFunction(&#x27;window.Store !&#x3D; undefined&#x27;);
await page.evaluate(async () &#x3D;&gt; {
// safely unregister service workers
const registrations &#x3D; await navigator.serviceWorker.getRegistrations();
for (let registration of registrations) {
registration.unregister();
}
});
//Load util functions (serializers, helper functions)
await page.evaluate(LoadUtils);
// Expose client info
/**
* Current connection information
* @type {ClientInfo}
*/
this.info &#x3D; new ClientInfo(this, await page.evaluate(() &#x3D;&gt; {
return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
}));
// Add InterfaceController
this.interface &#x3D; new InterfaceController(this);
// Register events
await page.exposeFunction(&#x27;onAddMessageEvent&#x27;, msg &#x3D;&gt; {
if (msg.type &#x3D;&#x3D;&#x3D; &#x27;gp2&#x27;) {
const notification &#x3D; new GroupNotification(this, msg);
if (msg.subtype &#x3D;&#x3D;&#x3D; &#x27;add&#x27; || msg.subtype &#x3D;&#x3D;&#x3D; &#x27;invite&#x27;) {
/**
* 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 &#x3D;&#x3D;&#x3D; &#x27;remove&#x27; || msg.subtype &#x3D;&#x3D;&#x3D; &#x27;leave&#x27;) {
/**
* 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 {
/**
* Emitted when group settings are updated, such as subject, description or picture.
* @event Client#group_update
* @param {GroupNotification} notification GroupNotification with more information about the action
*/
this.emit(Events.GROUP_UPDATE, notification);
}
return;
}
const message &#x3D; new Message(this, msg);
/**
* Emitted when a new message is created, which may include the current user&#x27;s own messages.
* @event Client#message_create
* @param {Message} message The message that was created
*/
this.emit(Events.MESSAGE_CREATE, message);
if (msg.id.fromMe) return;
/**
* Emitted when a new message is received.
* @event Client#message
* @param {Message} message The message that was received
*/
this.emit(Events.MESSAGE_RECEIVED, message);
});
let last_message;
await page.exposeFunction(&#x27;onChangeMessageTypeEvent&#x27;, (msg) &#x3D;&gt; {
if (msg.type &#x3D;&#x3D;&#x3D; &#x27;revoked&#x27;) {
const message &#x3D; new Message(this, msg);
let revoked_msg;
if (last_message &amp;amp;&amp;amp; msg.id.id &#x3D;&#x3D;&#x3D; last_message.id.id) {
revoked_msg &#x3D; 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&#x27;s data.
* @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message&#x27;s original data.
* Note that due to the way this data is captured, it may be possible that this param will be undefined.
*/
this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
}
});
await page.exposeFunction(&#x27;onChangeMessageEvent&#x27;, (msg) &#x3D;&gt; {
if (msg.type !&#x3D;&#x3D; &#x27;revoked&#x27;) {
last_message &#x3D; msg;
}
});
await page.exposeFunction(&#x27;onRemoveMessageEvent&#x27;, (msg) &#x3D;&gt; {
if (!msg.isNewMsg) return;
const message &#x3D; new Message(this, msg);
/**
* Emitted when a message is deleted by the current user.
* @event Client#message_revoke_me
* @param {Message} message The message that was revoked
*/
this.emit(Events.MESSAGE_REVOKED_ME, message);
});
await page.exposeFunction(&#x27;onMessageAckEvent&#x27;, (msg, ack) &#x3D;&gt; {
const message &#x3D; new Message(this, msg);
/**
* Emitted when an ack event occurrs on message type.
* @event Client#message_ack
* @param {Message} message The message that was affected
* @param {MessageAck} ack The new ACK value
*/
this.emit(Events.MESSAGE_ACK, message, ack);
});
await page.exposeFunction(&#x27;onMessageMediaUploadedEvent&#x27;, (msg) &#x3D;&gt; {
const message &#x3D; 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(&#x27;onAppStateChangedEvent&#x27;, async (state) &#x3D;&gt; {
/**
* Emitted when the connection state changes
* @event Client#change_state
* @param {WAState} state the new connection state
*/
this.emit(Events.STATE_CHANGED, state);
const ACCEPTED_STATES &#x3D; [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
if (this.options.takeoverOnConflict) {
ACCEPTED_STATES.push(WAState.CONFLICT);
if (state &#x3D;&#x3D;&#x3D; WAState.CONFLICT) {
setTimeout(() &#x3D;&gt; {
this.pupPage.evaluate(() &#x3D;&gt; window.Store.AppState.takeover());
}, this.options.takeoverTimeoutMs);
}
}
if (!ACCEPTED_STATES.includes(state)) {
/**
* Emitted when the client has been disconnected
* @event Client#disconnected
* @param {WAState|&quot;NAVIGATION&quot;} reason reason that caused the disconnect
*/
await this.authStrategy.disconnect();
this.emit(Events.DISCONNECTED, state);
this.destroy();
}
});
await page.exposeFunction(&#x27;onBatteryStateChangedEvent&#x27;, (state) &#x3D;&gt; {
const { battery, plugged } &#x3D; state;
if (battery &#x3D;&#x3D;&#x3D; undefined) return;
/**
* Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
* @event Client#change_battery
* @param {object} batteryInfo
* @param {number} batteryInfo.battery - The current battery percentage
* @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
* @deprecated
*/
this.emit(Events.BATTERY_CHANGED, { battery, plugged });
});
await page.exposeFunction(&#x27;onIncomingCall&#x27;, (call) &#x3D;&gt; {
/**
* 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 &#x3D; new Call(this, call);
this.emit(Events.INCOMING_CALL, cll);
});
await page.exposeFunction(&#x27;onReaction&#x27;, (reactions) &#x3D;&gt; {
for (const reaction of reactions) {
/**
* Emitted when a reaction is sent, received, updated or removed
* @event Client#message_reaction
* @param {object} reaction
* @param {object} reaction.id - Reaction id
* @param {number} reaction.orphan - Orphan
* @param {?string} reaction.orphanReason - Orphan reason
* @param {number} reaction.timestamp - Timestamp
* @param {string} reaction.reaction - Reaction
* @param {boolean} reaction.read - Read
* @param {object} reaction.msgId - Parent message id
* @param {string} reaction.senderId - Sender id
* @param {?number} reaction.ack - Ack
*/
this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
}
});
await page.evaluate(() &#x3D;&gt; {
window.Store.Msg.on(&#x27;change&#x27;, (msg) &#x3D;&gt; { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
window.Store.Msg.on(&#x27;change:type&#x27;, (msg) &#x3D;&gt; { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
window.Store.Msg.on(&#x27;change:ack&#x27;, (msg, ack) &#x3D;&gt; { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
window.Store.Msg.on(&#x27;change:isUnsentMedia&#x27;, (msg, unsent) &#x3D;&gt; { if (msg.id.fromMe &amp;amp;&amp;amp; !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); });
window.Store.Msg.on(&#x27;remove&#x27;, (msg) &#x3D;&gt; { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); });
window.Store.AppState.on(&#x27;change:state&#x27;, (_AppState, state) &#x3D;&gt; { window.onAppStateChangedEvent(state); });
window.Store.Conn.on(&#x27;change:battery&#x27;, (state) &#x3D;&gt; { window.onBatteryStateChangedEvent(state); });
window.Store.Call.on(&#x27;add&#x27;, (call) &#x3D;&gt; { window.onIncomingCall(call); });
window.Store.Msg.on(&#x27;add&#x27;, (msg) &#x3D;&gt; {
if (msg.isNewMsg) {
if(msg.type &#x3D;&#x3D;&#x3D; &#x27;ciphertext&#x27;) {
// defer message event until ciphertext is resolved (type changed)
msg.once(&#x27;change:type&#x27;, (_msg) &#x3D;&gt; window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg)));
} else {
window.onAddMessageEvent(window.WWebJS.getMessageModel(msg));
}
}
});
{
const module &#x3D; window.Store.createOrUpdateReactionsModule;
const ogMethod &#x3D; module.createOrUpdateReactions;
module.createOrUpdateReactions &#x3D; ((...args) &#x3D;&gt; {
window.onReaction(args[0].map(reaction &#x3D;&gt; {
const msgKey &#x3D; window.Store.MsgKey.fromString(reaction.msgKey);
const parentMsgKey &#x3D; window.Store.MsgKey.fromString(reaction.parentMsgKey);
const timestamp &#x3D; reaction.timestamp / 1000;
return {...reaction, msgKey, parentMsgKey, timestamp };
}));
return ogMethod(...args);
}).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(&#x27;framenavigated&#x27;, async () &#x3D;&gt; {
const appState &#x3D; await this.getState();
if(!appState || appState &#x3D;&#x3D;&#x3D; WAState.PAIRING) {
await this.authStrategy.disconnect();
this.emit(Events.DISCONNECTED, &#x27;NAVIGATION&#x27;);
await this.destroy();
}
});
}
/**
* Closes the client
*/
async destroy() {
await this.pupBrowser.close();
await this.authStrategy.destroy();
}
/**
* Logs out the client, closing the current session
*/
async logout() {
await this.pupPage.evaluate(() &#x3D;&gt; {
return window.Store.AppState.logout();
});
await this.authStrategy.logout();
}
/**
* Returns the version of WhatsApp Web currently being run
* @returns {Promise&amp;lt;string&gt;}
*/
async getWWebVersion() {
return await this.pupPage.evaluate(() &#x3D;&gt; {
return window.Debug.VERSION;
});
}
/**
* Mark as seen for the Chat
* @param {string} chatId
* @returns {Promise&amp;lt;boolean&gt;} result
*
*/
async sendSeen(chatId) {
const result &#x3D; await this.pupPage.evaluate(async (chatId) &#x3D;&gt; {
return window.WWebJS.sendSeen(chatId);
}, chatId);
return result;
}
/**
* Message options.
* @typedef {Object} MessageSendOptions
* @property {boolean} [linkPreview&#x3D;true] - Show links preview. Has no effect on multi-device accounts.
* @property {boolean} [sendAudioAsVoice&#x3D;false] - Send audio as voice message
* @property {boolean} [sendVideoAsGif&#x3D;false] - Send video as gif
* @property {boolean} [sendMediaAsSticker&#x3D;false] - Send media as a sticker
* @property {boolean} [sendMediaAsDocument&#x3D;false] - Send media as a document
* @property {boolean} [parseVCards&#x3D;true] - Automatically parse vCards and send them as contacts
* @property {string} [caption] - Image or video caption
* @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to)
* @property {Contact[]} [mentions] - Contacts that are being mentioned in the message
* @property {boolean} [sendSeen&#x3D;true] - Mark the conversation as seen after sending the message
* @property {string} [stickerAuthor&#x3D;undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true).
* @property {string} [stickerName&#x3D;undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
* @property {string[]} [stickerCategories&#x3D;undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
* @property {MessageMedia} [media] - Media to be sent
*/
/**
* Send a message to a specific chatId
* @param {string} chatId
* @param {string|MessageMedia|Location|Contact|Array&amp;lt;Contact&gt;|Buttons|List} content
* @param {MessageSendOptions} [options] - Options used when sending the message
*
* @returns {Promise&amp;lt;Message&gt;} Message that was just sent
*/
async sendMessage(chatId, content, options &#x3D; {}) {
let internalOptions &#x3D; {
linkPreview: options.linkPreview &#x3D;&#x3D;&#x3D; false ? undefined : true,
sendAudioAsVoice: options.sendAudioAsVoice,
sendVideoAsGif: options.sendVideoAsGif,
sendMediaAsSticker: options.sendMediaAsSticker,
sendMediaAsDocument: options.sendMediaAsDocument,
caption: options.caption,
quotedMessageId: options.quotedMessageId,
parseVCards: options.parseVCards &#x3D;&#x3D;&#x3D; false ? false : true,
mentionedJidList: Array.isArray(options.mentions) ? options.mentions.map(contact &#x3D;&gt; contact.id._serialized) : [],
extraOptions: options.extra
};
const sendSeen &#x3D; typeof options.sendSeen &#x3D;&#x3D;&#x3D; &#x27;undefined&#x27; ? true : options.sendSeen;
if (content instanceof MessageMedia) {
internalOptions.attachment &#x3D; content;
content &#x3D; &#x27;&#x27;;
} else if (options.media instanceof MessageMedia) {
internalOptions.attachment &#x3D; options.media;
internalOptions.caption &#x3D; content;
content &#x3D; &#x27;&#x27;;
} else if (content instanceof Location) {
internalOptions.location &#x3D; content;
content &#x3D; &#x27;&#x27;;
} else if (content instanceof Contact) {
internalOptions.contactCard &#x3D; content.id._serialized;
content &#x3D; &#x27;&#x27;;
} else if (Array.isArray(content) &amp;amp;&amp;amp; content.length &gt; 0 &amp;amp;&amp;amp; content[0] instanceof Contact) {
internalOptions.contactCardList &#x3D; content.map(contact &#x3D;&gt; contact.id._serialized);
content &#x3D; &#x27;&#x27;;
} else if (content instanceof Buttons) {
if (content.type !&#x3D;&#x3D; &#x27;chat&#x27;) { internalOptions.attachment &#x3D; content.body; }
internalOptions.buttons &#x3D; content;
content &#x3D; &#x27;&#x27;;
} else if (content instanceof List) {
internalOptions.list &#x3D; content;
content &#x3D; &#x27;&#x27;;
}
if (internalOptions.sendMediaAsSticker &amp;amp;&amp;amp; internalOptions.attachment) {
internalOptions.attachment &#x3D; await Util.formatToWebpSticker(
internalOptions.attachment, {
name: options.stickerName,
author: options.stickerAuthor,
categories: options.stickerCategories
}, this.pupPage
);
}
const newMessage &#x3D; await this.pupPage.evaluate(async (chatId, message, options, sendSeen) &#x3D;&gt; {
const chatWid &#x3D; window.Store.WidFactory.createWid(chatId);
const chat &#x3D; await window.Store.Chat.find(chatWid);
if (sendSeen) {
window.WWebJS.sendSeen(chatId);
}
const msg &#x3D; await window.WWebJS.sendMessage(chat, message, options, sendSeen);
return msg.serialize();
}, chatId, content, internalOptions, sendSeen);
return new Message(this, newMessage);
}
/**
* Searches for messages
* @param {string} query
* @param {Object} [options]
* @param {number} [options.page]
* @param {number} [options.limit]
* @param {string} [options.chatId]
* @returns {Promise&amp;lt;Message[]&gt;}
*/
async searchMessages(query, options &#x3D; {}) {
const messages &#x3D; await this.pupPage.evaluate(async (query, page, count, remote) &#x3D;&gt; {
const { messages } &#x3D; await window.Store.Msg.search(query, page, count, remote);
return messages.map(msg &#x3D;&gt; window.WWebJS.getMessageModel(msg));
}, query, options.page, options.limit, options.chatId);
return messages.map(msg &#x3D;&gt; new Message(this, msg));
}
/**
* Get all current chat instances
* @returns {Promise&amp;lt;Array&amp;lt;Chat&gt;&gt;}
*/
async getChats() {
let chats &#x3D; await this.pupPage.evaluate(async () &#x3D;&gt; {
return await window.WWebJS.getChats();
});
return chats.map(chat &#x3D;&gt; ChatFactory.create(this, chat));
}
/**
* Get chat instance by ID
* @param {string} chatId
* @returns {Promise&amp;lt;Chat&gt;}
*/
async getChatById(chatId) {
let chat &#x3D; await this.pupPage.evaluate(async chatId &#x3D;&gt; {
return await window.WWebJS.getChat(chatId);
}, chatId);
return ChatFactory.create(this, chat);
}
/**
* Get all current contact instances
* @returns {Promise&amp;lt;Array&amp;lt;Contact&gt;&gt;}
*/
async getContacts() {
let contacts &#x3D; await this.pupPage.evaluate(() &#x3D;&gt; {
return window.WWebJS.getContacts();
});
return contacts.map(contact &#x3D;&gt; ContactFactory.create(this, contact));
}
/**
* Get contact instance by ID
* @param {string} contactId
* @returns {Promise&amp;lt;Contact&gt;}
*/
async getContactById(contactId) {
let contact &#x3D; await this.pupPage.evaluate(contactId &#x3D;&gt; {
return window.WWebJS.getContact(contactId);
}, contactId);
return ContactFactory.create(this, contact);
}
/**
* Returns an object with information about the invite code&#x27;s group
* @param {string} inviteCode
* @returns {Promise&amp;lt;object&gt;} Invite information
*/
async getInviteInfo(inviteCode) {
return await this.pupPage.evaluate(inviteCode &#x3D;&gt; {
return window.Store.InviteInfo.queryGroupInvite(inviteCode);
}, inviteCode);
}
/**
* Accepts an invitation to join a group
* @param {string} inviteCode Invitation code
* @returns {Promise&amp;lt;string&gt;} Id of the joined Chat
*/
async acceptInvite(inviteCode) {
const res &#x3D; await this.pupPage.evaluate(async inviteCode &#x3D;&gt; {
return await window.Store.Invite.joinGroupViaInvite(inviteCode);
}, inviteCode);
return res.gid._serialized;
}
/**
* Accepts a private invitation to join a group
* @param {object} inviteInfo Invite V4 Info
* @returns {Promise&amp;lt;Object&gt;}
*/
async acceptGroupV4Invite(inviteInfo) {
if (!inviteInfo.inviteCode) throw &#x27;Invalid invite code, try passing the message.inviteV4 object&#x27;;
if (inviteInfo.inviteCodeExp &#x3D;&#x3D; 0) throw &#x27;Expired invite code&#x27;;
return this.pupPage.evaluate(async inviteInfo &#x3D;&gt; {
let { groupId, fromId, inviteCode, inviteCodeExp } &#x3D; inviteInfo;
return await window.Store.JoinInviteV4.sendJoinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, fromId);
}, inviteInfo);
}
/**
* Sets the current user&#x27;s status message
* @param {string} status New status message
*/
async setStatus(status) {
await this.pupPage.evaluate(async status &#x3D;&gt; {
return await window.Store.StatusUtils.setMyStatus(status);
}, status);
}
/**
* Sets the current user&#x27;s display name.
* This is the name shown to WhatsApp users that have not added you as a contact beside your number in groups and in your profile.
* @param {string} displayName New display name
* @returns {Promise&amp;lt;Boolean&gt;}
*/
async setDisplayName(displayName) {
const couldSet &#x3D; await this.pupPage.evaluate(async displayName &#x3D;&gt; {
if(!window.Store.Conn.canSetMyPushname()) return false;
if(window.Store.MDBackend) {
// TODO
return false;
} else {
const res &#x3D; await window.Store.Wap.setPushname(displayName);
return !res.status || res.status &#x3D;&#x3D;&#x3D; 200;
}
}, displayName);
return couldSet;
}
/**
* Gets the current connection state for the client
* @returns {WAState}
*/
async getState() {
return await this.pupPage.evaluate(() &#x3D;&gt; {
if(!window.Store) return null;
return window.Store.AppState.state;
});
}
/**
* Marks the client as online
*/
async sendPresenceAvailable() {
return await this.pupPage.evaluate(() &#x3D;&gt; {
return window.Store.PresenceUtils.sendPresenceAvailable();
});
}
/**
* Marks the client as unavailable
*/
async sendPresenceUnavailable() {
return await this.pupPage.evaluate(() &#x3D;&gt; {
return window.Store.PresenceUtils.sendPresenceUnavailable();
});
}
/**
* Enables and returns the archive state of the Chat
* @returns {boolean}
*/
async archiveChat(chatId) {
return await this.pupPage.evaluate(async chatId &#x3D;&gt; {
let chat &#x3D; await window.Store.Chat.get(chatId);
await window.Store.Cmd.archiveChat(chat, true);
return true;
}, chatId);
}
/**
* Changes and returns the archive state of the Chat
* @returns {boolean}
*/
async unarchiveChat(chatId) {
return await this.pupPage.evaluate(async chatId &#x3D;&gt; {
let chat &#x3D; await window.Store.Chat.get(chatId);
await window.Store.Cmd.archiveChat(chat, false);
return false;
}, chatId);
}
/**
* Pins the Chat
* @returns {Promise&amp;lt;boolean&gt;} New pin state. Could be false if the max number of pinned chats was reached.
*/
async pinChat(chatId) {
return this.pupPage.evaluate(async chatId &#x3D;&gt; {
let chat &#x3D; window.Store.Chat.get(chatId);
if (chat.pin) {
return true;
}
const MAX_PIN_COUNT &#x3D; 3;
const chatModels &#x3D; window.Store.Chat.getModelsArray();
if (chatModels.length &gt; MAX_PIN_COUNT) {
let maxPinned &#x3D; chatModels[MAX_PIN_COUNT - 1].pin;
if (maxPinned) {
return false;
}
}
await window.Store.Cmd.pinChat(chat, true);
return true;
}, chatId);
}
/**
* Unpins the Chat
* @returns {Promise&amp;lt;boolean&gt;} New pin state
*/
async unpinChat(chatId) {
return this.pupPage.evaluate(async chatId &#x3D;&gt; {
let chat &#x3D; window.Store.Chat.get(chatId);
if (!chat.pin) {
return false;
}
await window.Store.Cmd.pinChat(chat, false);
return false;
}, chatId);
}
/**
* Mutes this chat forever, unless a date is specified
* @param {string} chatId ID of the chat that will be muted
* @param {?Date} unmuteDate Date when the chat will be unmuted, leave as is to mute forever
*/
async muteChat(chatId, unmuteDate) {
unmuteDate &#x3D; unmuteDate ? unmuteDate.getTime() / 1000 : -1;
await this.pupPage.evaluate(async (chatId, timestamp) &#x3D;&gt; {
let chat &#x3D; await window.Store.Chat.get(chatId);
await chat.mute.mute(timestamp, !0);
}, chatId, unmuteDate || -1);
}
/**
* Unmutes the Chat
* @param {string} chatId ID of the chat that will be unmuted
*/
async unmuteChat(chatId) {
await this.pupPage.evaluate(async chatId &#x3D;&gt; {
let chat &#x3D; await window.Store.Chat.get(chatId);
await window.Store.Cmd.muteChat(chat, false);
}, chatId);
}
/**
* Mark the Chat as unread
* @param {string} chatId ID of the chat that will be marked as unread
*/
async markChatUnread(chatId) {
await this.pupPage.evaluate(async chatId &#x3D;&gt; {
let chat &#x3D; await window.Store.Chat.get(chatId);
await window.Store.Cmd.markChatUnread(chat, true);
}, chatId);
}
/**
* Returns the contact ID&#x27;s profile picture URL, if privacy settings allow it
* @param {string} contactId the whatsapp user&#x27;s ID
* @returns {Promise&amp;lt;string&gt;}
*/
async getProfilePicUrl(contactId) {
const profilePic &#x3D; await this.pupPage.evaluate(async contactId &#x3D;&gt; {
try {
const chatWid &#x3D; window.Store.WidFactory.createWid(contactId);
return await window.Store.ProfilePic.profilePicFind(chatWid);
} catch (err) {
if(err.name &#x3D;&#x3D;&#x3D; &#x27;ServerStatusCodeError&#x27;) return undefined;
throw err;
}
}, contactId);
return profilePic ? profilePic.eurl : undefined;
}
/**
* Gets the Contact&#x27;s common groups with you. Returns empty array if you don&#x27;t have any common group.
* @param {string} contactId the whatsapp user&#x27;s ID (_serialized format)
* @returns {Promise&amp;lt;WAWebJS.ChatId[]&gt;}
*/
async getCommonGroups(contactId) {
const commonGroups &#x3D; await this.pupPage.evaluate(async (contactId) &#x3D;&gt; {
let contact &#x3D; window.Store.Contact.get(contactId);
if (!contact) {
const wid &#x3D; window.Store.WidFactory.createUserWid(contactId);
const chatConstructor &#x3D; window.Store.Contact.getModelsArray().find(c&#x3D;&gt;!c.isGroup).constructor;
contact &#x3D; new chatConstructor({id: wid});
}
if (contact.commonGroups) {
return contact.commonGroups.serialize();
}
const status &#x3D; await window.Store.findCommonGroups(contact);
if (status) {
return contact.commonGroups.serialize();
}
return [];
}, contactId);
const chats &#x3D; [];
for (const group of commonGroups) {
chats.push(group.id);
}
return chats;
}
/**
* Force reset of connection state for the client
*/
async resetState() {
await this.pupPage.evaluate(() &#x3D;&gt; {
window.Store.AppState.phoneWatchdog.shiftTimer.forceRunNow();
});
}
/**
* Check if a given ID is registered in whatsapp
* @param {string} id the whatsapp user&#x27;s ID
* @returns {Promise&amp;lt;Boolean&gt;}
*/
async isRegisteredUser(id) {
return Boolean(await this.getNumberId(id));
}
/**
* Get the registered WhatsApp ID for a number.
* Will return null if the number is not registered on WhatsApp.
* @param {string} number Number or ID (&quot;@c.us&quot; will be automatically appended if not specified)
* @returns {Promise&amp;lt;Object|null&gt;}
*/
async getNumberId(number) {
if (!number.endsWith(&#x27;@c.us&#x27;)) {
number +&#x3D; &#x27;@c.us&#x27;;
}
return await this.pupPage.evaluate(async number &#x3D;&gt; {
const wid &#x3D; window.Store.WidFactory.createWid(number);
const result &#x3D; await window.Store.QueryExist(wid);
if (!result || result.wid &#x3D;&#x3D;&#x3D; undefined) return null;
return result.wid;
}, number);
}
/**
* Get the formatted number of a WhatsApp ID.
* @param {string} number Number or ID
* @returns {Promise&amp;lt;string&gt;}
*/
async getFormattedNumber(number) {
if (!number.endsWith(&#x27;@s.whatsapp.net&#x27;)) number &#x3D; number.replace(&#x27;c.us&#x27;, &#x27;s.whatsapp.net&#x27;);
if (!number.includes(&#x27;@s.whatsapp.net&#x27;)) number &#x3D; &#x60;${number}@s.whatsapp.net&#x60;;
return await this.pupPage.evaluate(async numberId &#x3D;&gt; {
return window.Store.NumberInfo.formattedPhoneNumber(numberId);
}, number);
}
/**
* Get the country code of a WhatsApp ID.
* @param {string} number Number or ID
* @returns {Promise&amp;lt;string&gt;}
*/
async getCountryCode(number) {
number &#x3D; number.replace(&#x27; &#x27;, &#x27;&#x27;).replace(&#x27;+&#x27;, &#x27;&#x27;).replace(&#x27;@c.us&#x27;, &#x27;&#x27;);
return await this.pupPage.evaluate(async numberId &#x3D;&gt; {
return window.Store.NumberInfo.findCC(numberId);
}, number);
}
/**
* Create a new group
* @param {string} name group title
* @param {Array&amp;lt;Contact|string&gt;} participants an array of Contacts or contact IDs to add to the group
* @returns {Object} createRes
* @returns {string} createRes.gid - ID for the group that was just created
* @returns {Object.&amp;lt;string,string&gt;} createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user&#x27;s privacy settings don&#x27;t allow you to add them to groups.
*/
async createGroup(name, participants) {
if (!Array.isArray(participants) || participants.length &#x3D;&#x3D; 0) {
throw &#x27;You need to add at least one other participant to the group&#x27;;
}
if (participants.every(c &#x3D;&gt; c instanceof Contact)) {
participants &#x3D; participants.map(c &#x3D;&gt; c.id._serialized);
}
const createRes &#x3D; await this.pupPage.evaluate(async (name, participantIds) &#x3D;&gt; {
const participantWIDs &#x3D; participantIds.map(p &#x3D;&gt; window.Store.WidFactory.createWid(p));
return await window.Store.GroupUtils.createGroup(name, participantWIDs, 0);
}, name, participants);
const missingParticipants &#x3D; createRes.participants.reduce(((missing, c) &#x3D;&gt; {
const id &#x3D; c.wid._serialized;
const statusCode &#x3D; c.error ? c.error.toString() : &#x27;200&#x27;;
if (statusCode !&#x3D; 200) return Object.assign(missing, { [id]: statusCode });
return missing;
}), {});
return { gid: createRes.wid, missingParticipants };
}
/**
* Get all current Labels
* @returns {Promise&amp;lt;Array&amp;lt;Label&gt;&gt;}
*/
async getLabels() {
const labels &#x3D; await this.pupPage.evaluate(async () &#x3D;&gt; {
return window.WWebJS.getLabels();
});
return labels.map(data &#x3D;&gt; new Label(this, data));
}
/**
* Get Label instance by ID
* @param {string} labelId
* @returns {Promise&amp;lt;Label&gt;}
*/
async getLabelById(labelId) {
const label &#x3D; await this.pupPage.evaluate(async (labelId) &#x3D;&gt; {
return window.WWebJS.getLabel(labelId);
}, labelId);
return new Label(this, label);
}
/**
* Get all Labels assigned to a chat
* @param {string} chatId
* @returns {Promise&amp;lt;Array&amp;lt;Label&gt;&gt;}
*/
async getChatLabels(chatId) {
const labels &#x3D; await this.pupPage.evaluate(async (chatId) &#x3D;&gt; {
return window.WWebJS.getChatLabels(chatId);
}, chatId);
return labels.map(data &#x3D;&gt; new Label(this, data));
}
/**
* Get all Chats for a specific Label
* @param {string} labelId
* @returns {Promise&amp;lt;Array&amp;lt;Chat&gt;&gt;}
*/
async getChatsByLabelId(labelId) {
const chatIds &#x3D; await this.pupPage.evaluate(async (labelId) &#x3D;&gt; {
const label &#x3D; window.Store.Label.get(labelId);
const labelItems &#x3D; label.labelItemCollection.getModelsArray();
return labelItems.reduce((result, item) &#x3D;&gt; {
if (item.parentType &#x3D;&#x3D;&#x3D; &#x27;Chat&#x27;) {
result.push(item.parentId);
}
return result;
}, []);
}, labelId);
return Promise.all(chatIds.map(id &#x3D;&gt; this.getChatById(id)));
}
/**
* Gets all blocked contacts by host account
* @returns {Promise&amp;lt;Array&amp;lt;Contact&gt;&gt;}
*/
async getBlockedContacts() {
const blockedContacts &#x3D; await this.pupPage.evaluate(() &#x3D;&gt; {
let chatIds &#x3D; window.Store.Blocklist.getModelsArray().map(a &#x3D;&gt; a.id._serialized);
return Promise.all(chatIds.map(id &#x3D;&gt; window.WWebJS.getContact(id)));
});
return blockedContacts.map(contact &#x3D;&gt; ContactFactory.create(this.client, contact));
}
}
module.exports &#x3D; Client;
</code></pre>
</article>
</div>
</div>
<nav id="jsdoc-toc-nav" role="navigation"></nav>
</div>
</div>
<footer id="jsdoc-footer" class="jsdoc-footer">
<div id="jsdoc-footer-container">
<p>
Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc</a> 3.6.7 on January 15, 2023.
</p>
</div>
</footer>
<script src="scripts/jquery.min.js"></script>
<script src="scripts/tree.jquery.js"></script>
<script src="scripts/prettify.js"></script>
<script src="scripts/jsdoc-toc.js"></script>
<script src="scripts/linenumber.js"></script>
<script src="scripts/scrollanchor.js"></script>
</body>
</html>