new changes
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="generator" content="JSDoc 3.6.3">
|
||||
<meta name="generator" content="JSDoc 3.6.4">
|
||||
<meta charset="utf-8">
|
||||
<title>whatsapp-web.js 1.2.1-post » Source: Client.js</title>
|
||||
<title>whatsapp-web.js 1.7.0 » 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">
|
||||
@@ -15,7 +15,7 @@
|
||||
<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>2.<wbr>1-post</a>
|
||||
<a href="index.html" class="jsdoc-navbar-package-name">whatsapp-web.<wbr>js 1.<wbr>7.<wbr>0</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -33,15 +33,16 @@
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const puppeteer = require('puppeteer');
|
||||
const moduleRaid = require('moduleraid/moduleraid');
|
||||
const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');
|
||||
const jsQR = require('jsqr');
|
||||
|
||||
const Util = require('./util/Util');
|
||||
const InterfaceController = require('./util/InterfaceController');
|
||||
const { WhatsWebURL, UserAgent, DefaultOptions, Events, WAState } = require('./util/Constants');
|
||||
const { ExposeStore, LoadUtils } = require('./util/Injected');
|
||||
const ChatFactory = require('./factories/ChatFactory');
|
||||
const ContactFactory = require('./factories/ContactFactory');
|
||||
const { ClientInfo, Message, MessageMedia, Location, GroupNotification } = require('./structures');
|
||||
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification } = require('./structures');
|
||||
/**
|
||||
* Starting point for interacting with the WhatsApp Web API
|
||||
* @extends {EventEmitter}
|
||||
@@ -54,11 +55,13 @@ const { ClientInfo, Message, MessageMedia, Location, GroupNotification } =
|
||||
* @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
|
||||
* @fires Client#change_battery
|
||||
*/
|
||||
class Client extends EventEmitter {
|
||||
constructor(options = {}) {
|
||||
@@ -78,6 +81,9 @@ class Client extends EventEmitter {
|
||||
const page = (await browser.pages())[0];
|
||||
page.setUserAgent(UserAgent);
|
||||
|
||||
this.pupBrowser = browser;
|
||||
this.pupPage = page;
|
||||
|
||||
if (this.options.session) {
|
||||
await page.evaluateOnNewDocument(
|
||||
session => {
|
||||
@@ -89,14 +95,17 @@ class Client extends EventEmitter {
|
||||
}, this.options.session);
|
||||
}
|
||||
|
||||
await page.goto(WhatsWebURL);
|
||||
|
||||
const KEEP_PHONE_CONNECTED_IMG_SELECTOR = '[data-asset-intro-image="true"]';
|
||||
await page.goto(WhatsWebURL, {
|
||||
waitUntil: 'load',
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
const KEEP_PHONE_CONNECTED_IMG_SELECTOR = '[data-asset-intro-image-light="true"]';
|
||||
|
||||
if (this.options.session) {
|
||||
// Check if session restore was successfull
|
||||
try {
|
||||
await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: 15000 });
|
||||
await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: this.options.authTimeoutMs });
|
||||
} catch (err) {
|
||||
if (err.name === 'TimeoutError') {
|
||||
/**
|
||||
@@ -106,7 +115,11 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
this.emit(Events.AUTHENTICATION_FAILURE, 'Unable to log in. Are the session details valid?');
|
||||
browser.close();
|
||||
|
||||
if (this.options.restartOnAuthFail) {
|
||||
// session restore failed so try again but without session to force new authentication
|
||||
this.options.session = null;
|
||||
this.initialize();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,7 +138,7 @@ class Client extends EventEmitter {
|
||||
// Wait for QR Code
|
||||
|
||||
const QR_CANVAS_SELECTOR = 'canvas';
|
||||
await page.waitForSelector(QR_CANVAS_SELECTOR);
|
||||
await page.waitForSelector(QR_CANVAS_SELECTOR, { timeout: this.options.qrTimeoutMs });
|
||||
const qrImgData = await page.$eval(QR_CANVAS_SELECTOR, canvas => [].slice.call(canvas.getContext('2d').getImageData(0, 0, 264, 264).data));
|
||||
const qr = jsQR(qrImgData, 264, 264).data;
|
||||
/**
|
||||
@@ -136,11 +149,12 @@ class Client extends EventEmitter {
|
||||
this.emit(Events.QR_RECEIVED, qr);
|
||||
};
|
||||
getQrCode();
|
||||
let retryInterval = setInterval(getQrCode, 20000); // check for qr code every 20 seconds
|
||||
this._qrRefreshInterval = setInterval(getQrCode, this.options.qrRefreshIntervalMs);
|
||||
|
||||
// Wait for code scan
|
||||
await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: 0 });
|
||||
clearInterval(retryInterval);
|
||||
clearInterval(this._qrRefreshInterval);
|
||||
this._qrRefreshInterval = undefined;
|
||||
|
||||
}
|
||||
|
||||
@@ -176,6 +190,9 @@ class Client extends EventEmitter {
|
||||
return window.Store.Conn.serialize();
|
||||
}));
|
||||
|
||||
// Add InterfaceController
|
||||
this.interface = new InterfaceController(this);
|
||||
|
||||
// Register events
|
||||
await page.exposeFunction('onAddMessageEvent', msg => {
|
||||
if (!msg.isNewMsg) return;
|
||||
@@ -286,7 +303,19 @@ class Client extends EventEmitter {
|
||||
|
||||
});
|
||||
|
||||
await page.exposeFunction('onAppStateChangedEvent', (_AppState, state) => {
|
||||
await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
|
||||
|
||||
const message = new Message(this, msg);
|
||||
|
||||
/**
|
||||
* Emitted when media has been uploaded for a message sent by the client.
|
||||
* @event Client#media_uploaded
|
||||
* @param {Message} message The message with media that was uploaded
|
||||
*/
|
||||
this.emit(Events.MEDIA_UPLOADED, message);
|
||||
});
|
||||
|
||||
await page.exposeFunction('onAppStateChangedEvent', (state) => {
|
||||
|
||||
/**
|
||||
* Emitted when the connection state changes
|
||||
@@ -296,6 +325,17 @@ class Client extends EventEmitter {
|
||||
this.emit(Events.STATE_CHANGED, state);
|
||||
|
||||
const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
|
||||
|
||||
if(this.options.takeoverOnConflict) {
|
||||
ACCEPTED_STATES.push(WAState.CONFLICT);
|
||||
|
||||
if(state === WAState.CONFLICT) {
|
||||
setTimeout(() => {
|
||||
this.pupPage.evaluate(() => window.Store.AppState.takeover());
|
||||
}, this.options.takeoverTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ACCEPTED_STATES.includes(state)) {
|
||||
/**
|
||||
* Emitted when the client has been disconnected
|
||||
@@ -307,17 +347,31 @@ class Client extends EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.Store.Msg.on('add', (msg) => { if(msg.isNewMsg) window.onAddMessageEvent(msg);});
|
||||
window.Store.Msg.on('change', window.onChangeMessageEvent);
|
||||
window.Store.Msg.on('change:type', window.onChangeMessageTypeEvent);
|
||||
window.Store.Msg.on('change:ack', window.onMessageAckEvent);
|
||||
window.Store.Msg.on('remove', window.onRemoveMessageEvent);
|
||||
window.Store.AppState.on('change:state', window.onAppStateChangedEvent);
|
||||
await page.exposeFunction('onBatteryStateChangedEvent', (state) => {
|
||||
const { battery, plugged } = state;
|
||||
|
||||
if(battery === undefined) return;
|
||||
|
||||
/**
|
||||
* Emitted when the battery percentage for the attached device changes
|
||||
* @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)
|
||||
*/
|
||||
this.emit(Events.BATTERY_CHANGED, { battery, plugged });
|
||||
});
|
||||
|
||||
this.pupBrowser = browser;
|
||||
this.pupPage = page;
|
||||
await page.evaluate(() => {
|
||||
window.Store.Msg.on('add', (msg) => { if(msg.isNewMsg) window.onAddMessageEvent(msg); });
|
||||
window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(msg); });
|
||||
window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(msg); });
|
||||
window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(msg, ack); });
|
||||
window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if(msg.id.fromMe &amp;&amp; !unsent) window.onMessageMediaUploadedEvent(msg); });
|
||||
window.Store.Msg.on('remove', (msg) => { if(msg.isNewMsg) window.onRemoveMessageEvent(msg); });
|
||||
window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
|
||||
window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
|
||||
});
|
||||
|
||||
/**
|
||||
* Emitted when the client has initialized and is ready to receive messages.
|
||||
@@ -330,8 +384,31 @@ class Client extends EventEmitter {
|
||||
* Closes the client
|
||||
*/
|
||||
async destroy() {
|
||||
if (this._qrRefreshInterval) {
|
||||
clearInterval(this._qrRefreshInterval);
|
||||
}
|
||||
await this.pupBrowser.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out the client, closing the current session
|
||||
*/
|
||||
async logout() {
|
||||
return await this.pupPage.evaluate(() => {
|
||||
return window.Store.AppState.logout();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of WhatsApp Web currently being run
|
||||
* @returns Promise&lt;string>
|
||||
*/
|
||||
async getWWebVersion() {
|
||||
return await this.pupPage.evaluate(() => {
|
||||
return window.Debug.VERSION;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark as seen for the Chat
|
||||
* @param {string} chatId
|
||||
@@ -345,6 +422,7 @@ class Client extends EventEmitter {
|
||||
}, chatId);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a specific chatId
|
||||
* @param {string} chatId
|
||||
@@ -354,6 +432,8 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
async sendMessage(chatId, content, options = {}) {
|
||||
let internalOptions = {
|
||||
linkPreview: options.linkPreview === false ? undefined : true,
|
||||
sendAudioAsVoice: options.sendAudioAsVoice,
|
||||
caption: options.caption,
|
||||
quotedMessageId: options.quotedMessageId,
|
||||
mentionedJidList: Array.isArray(options.mentions) ? options.mentions.map(contact => contact.id._serialized) : []
|
||||
@@ -367,38 +447,21 @@ class Client extends EventEmitter {
|
||||
} else if (options.media instanceof MessageMedia) {
|
||||
internalOptions.attachment = options.media;
|
||||
internalOptions.caption = content;
|
||||
content = '';
|
||||
} else if (content instanceof Location) {
|
||||
internalOptions.location = content;
|
||||
content = '';
|
||||
}
|
||||
|
||||
const newMessage = await this.pupPage.evaluate(async (chatId, message, options, sendSeen) => {
|
||||
let chat = window.Store.Chat.get(chatId);
|
||||
let msg;
|
||||
if (!chat) { // The chat is not available in the previously chatted list
|
||||
const chatWid = window.Store.WidFactory.createWid(chatId);
|
||||
const chat = await window.Store.Chat.find(chatWid);
|
||||
|
||||
let newChatId = await window.WWebJS.getNumberId(chatId);
|
||||
if (newChatId) {
|
||||
//get the topmost chat object and assign the new chatId to it .
|
||||
//This is just a workaround.May cause problem if there are no chats at all. Need to dig in and emulate how whatsapp web does
|
||||
let chat = window.Store.Chat.models[0];
|
||||
if (!chat)
|
||||
throw 'Chat List empty! Need at least one open conversation with any of your contact';
|
||||
|
||||
let originalChatObjId = chat.id;
|
||||
chat.id = newChatId;
|
||||
|
||||
msg = await window.WWebJS.sendMessage(chat, message, options);
|
||||
chat.id = originalChatObjId; //replace the chat with its original id
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(sendSeen) {
|
||||
window.WWebJS.sendSeen(chatId);
|
||||
}
|
||||
|
||||
msg = await window.WWebJS.sendMessage(chat, message, options, sendSeen);
|
||||
if(sendSeen) {
|
||||
window.WWebJS.sendSeen(chatId);
|
||||
}
|
||||
|
||||
const msg = await window.WWebJS.sendMessage(chat, message, options, sendSeen);
|
||||
return msg.serialize();
|
||||
}, chatId, content, internalOptions, sendSeen);
|
||||
|
||||
@@ -455,6 +518,17 @@ class Client extends EventEmitter {
|
||||
return ContactFactory.create(this, contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with information about the invite code's group
|
||||
* @param {string} inviteCode
|
||||
* @returns {Promise&lt;object>} Invite information
|
||||
*/
|
||||
async getInviteInfo(inviteCode) {
|
||||
return await this.pupPage.evaluate(inviteCode => {
|
||||
return window.Store.Wap.groupInviteInfo(inviteCode);
|
||||
}, inviteCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts an invitation to join a group
|
||||
* @param {string} inviteCode Invitation code
|
||||
@@ -487,6 +561,15 @@ class Client extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the client as online
|
||||
*/
|
||||
async sendPresenceAvailable() {
|
||||
return await this.pupPage.evaluate(() => {
|
||||
return window.Store.Wap.sendPresenceAvailable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables and returns the archive state of the Chat
|
||||
* @returns {boolean}
|
||||
@@ -511,6 +594,42 @@ class Client extends EventEmitter {
|
||||
}, chatId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the Chat until a specified date
|
||||
* @param {string} chatId ID of the chat that will be muted
|
||||
* @param {Date} unmuteDate Date when the chat will be unmuted
|
||||
*/
|
||||
async muteChat(chatId, unmuteDate) {
|
||||
await this.pupPage.evaluate(async (chatId, timestamp) => {
|
||||
let chat = await window.Store.Chat.get(chatId);
|
||||
await chat.mute.mute(timestamp, !0);
|
||||
}, chatId, unmuteDate.getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the Chat
|
||||
* @param {string} chatId ID of the chat that will be unmuted
|
||||
*/
|
||||
async unmuteChat(chatId) {
|
||||
await this.pupPage.evaluate(async chatId => {
|
||||
let chat = await window.Store.Chat.get(chatId);
|
||||
await window.Store.Cmd.muteChat(chat, false);
|
||||
}, chatId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contact ID's profile picture URL, if privacy settings allow it
|
||||
* @param {string} contactId the whatsapp user's ID
|
||||
* @returns {Promise&lt;string>}
|
||||
*/
|
||||
async getProfilePicUrl(contactId) {
|
||||
const profilePic = await this.pupPage.evaluate((contactId) => {
|
||||
return window.Store.Wap.profilePicFind(contactId);
|
||||
}, contactId);
|
||||
|
||||
return profilePic ? profilePic.eurl : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reset of connection state for the client
|
||||
*/
|
||||
@@ -520,6 +639,54 @@ class Client extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given ID is registered in whatsapp
|
||||
* @returns {Promise&lt;Boolean>}
|
||||
*/
|
||||
async isRegisteredUser(id) {
|
||||
return await this.pupPage.evaluate(async (id) => {
|
||||
let result = await window.Store.Wap.queryExist(id);
|
||||
return result.jid !== undefined;
|
||||
}, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new group
|
||||
* @param {string} name group title
|
||||
* @param {Array&lt;Contact|string>} 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.&lt;string,string>} 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's privacy settings don't allow you to add them to groups.
|
||||
*/
|
||||
async createGroup(name, participants) {
|
||||
if(!Array.isArray(participants) || participants.length == 0) {
|
||||
throw 'You need to add at least one other participant to the group';
|
||||
}
|
||||
|
||||
if(participants.every(c => c instanceof Contact)) {
|
||||
participants = participants.map(c => c.id._serialized);
|
||||
}
|
||||
|
||||
const createRes = await this.pupPage.evaluate(async (name, participantIds) => {
|
||||
const res = await window.Store.Wap.createGroup(name, participantIds);
|
||||
console.log(res);
|
||||
if(!res.status === 200) {
|
||||
throw 'An error occurred while creating the group!';
|
||||
}
|
||||
|
||||
return res;
|
||||
}, name, participants);
|
||||
|
||||
const missingParticipants = createRes.participants.reduce(((missing, c) => {
|
||||
const id = Object.keys(c)[0];
|
||||
const statusCode = c[id].code;
|
||||
if(statusCode != 200) return Object.assign(missing, {[id]: statusCode});
|
||||
return missing;
|
||||
}), {});
|
||||
|
||||
return { gid: createRes.gid, missingParticipants};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Client;
|
||||
@@ -533,7 +700,7 @@ module.exports = Client;
|
||||
<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.3 on March 13, 2020.
|
||||
Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc</a> 3.6.4 on June 18, 2020.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user