From 2f0480c80ee4ea1d57155c5ac439372f4450aa53 Mon Sep 17 00:00:00 2001 From: Pedro Lopez Date: Tue, 4 Feb 2020 23:04:51 -0400 Subject: [PATCH] docs: add more documentation --- src/Client.js | 70 ++++++++++++++++++++++++++++- src/structures/BusinessContact.js | 3 ++ src/structures/Chat.js | 29 ++++++++++++ src/structures/ClientInfo.js | 24 ++++++++++ src/structures/Contact.js | 32 +++++++++++++ src/structures/GroupChat.js | 12 +++-- src/structures/Message.js | 74 ++++++++++++++++++++++++++++--- src/util/Constants.js | 25 +++++++++++ src/util/Injected.js | 4 +- 9 files changed, 258 insertions(+), 15 deletions(-) diff --git a/src/Client.js b/src/Client.js index 399d415..b277e27 100644 --- a/src/Client.js +++ b/src/Client.js @@ -17,6 +17,15 @@ const MessageMedia = require('./structures/MessageMedia'); /** * Starting point for interacting with the WhatsApp Web API * @extends {EventEmitter} + * @fires Client#qr + * @fires Client#authenticated + * @fires Client#auth_failure + * @fires Client#ready + * @fires Client#message + * @fires Client#message_create + * @fires Client#message_revoke_me + * @fires Client#message_revoke_everyone + * @fires Client#disconnected */ class Client extends EventEmitter { constructor(options = {}) { @@ -57,6 +66,11 @@ class Client extends EventEmitter { await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: 15000 }); } catch (err) { if (err.name === 'TimeoutError') { + /** + * 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, 'Unable to log in. Are the session details valid?'); browser.close(); @@ -73,6 +87,11 @@ class Client extends EventEmitter { 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; + /** + * Emitted when the QR code is received + * @event Client#qr + * @param {string} qr QR Code + */ this.emit(Events.QR_RECEIVED, qr); // Wait for code scan @@ -93,6 +112,11 @@ class Client extends EventEmitter { WAToken2: localStorage.WAToken2 }; + /** + * Emitted when authentication is successful + * @event Client#authenticated + * @param {object} session Object containing session information. Can be used to restore the session. + */ this.emit(Events.AUTHENTICATED, session); // Check window.Store Injection @@ -111,9 +135,21 @@ class Client extends EventEmitter { if (!msg.isNewMsg) 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); }); @@ -127,6 +163,14 @@ class Client extends EventEmitter { 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. + */ this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg); } @@ -145,6 +189,12 @@ class Client extends EventEmitter { if (!msg.isNewMsg) return; const message = new Message(this, msg); + + /** + * Emitted when a message is deleted by the current user. + * @event Client#message_revoke_me + * @param {Message} message The message that was revoked + */ this.emit(Events.MESSAGE_REVOKED_ME, message); }); @@ -152,6 +202,10 @@ class Client extends EventEmitter { await page.exposeFunction('onAppStateChangedEvent', (AppState, state) => { const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING]; if (!ACCEPTED_STATES.includes(state)) { + /** + * Emitted when the client has been disconnected + * @event Client#disconnected + */ this.emit(Events.DISCONNECTED); this.destroy(); } @@ -168,9 +222,16 @@ class Client extends EventEmitter { this.pupBrowser = browser; this.pupPage = page; + /** + * Emitted when the client has initialized and is ready to receive messages. + * @event Client#ready + */ this.emit(Events.READY); } + /** + * Closes the client + */ async destroy() { await this.pupBrowser.close(); } @@ -180,6 +241,7 @@ class Client extends EventEmitter { * @param {string} chatId * @param {string|MessageMedia} content * @param {object} options + * @returns {Promise} Message that was just sent */ async sendMessage(chatId, content, options={}) { let internalOptions = { @@ -205,6 +267,7 @@ class Client extends EventEmitter { /** * Get all current chat instances + * @returns {Promise>} */ async getChats() { let chats = await this.pupPage.evaluate(() => { @@ -217,6 +280,7 @@ class Client extends EventEmitter { /** * Get chat instance by ID * @param {string} chatId + * @returns {Promise} */ async getChatById(chatId) { let chat = await this.pupPage.evaluate(chatId => { @@ -228,6 +292,7 @@ class Client extends EventEmitter { /** * Get all current contact instances + * @returns {Promise>} */ async getContacts() { let contacts = await this.pupPage.evaluate(() => { @@ -240,6 +305,7 @@ class Client extends EventEmitter { /** * Get contact instance by ID * @param {string} contactId + * @returns {Promise} */ async getContactById(contactId) { let contact = await this.pupPage.evaluate(contactId => { @@ -250,8 +316,8 @@ class Client extends EventEmitter { } /** - * Accepts an invite by code - * @param {string} inviteCode + * Accepts an invitation to join a group + * @param {string} inviteCode Invitation code */ async acceptInvite(inviteCode) { const chatId = await this.pupPage.evaluate(async inviteCode => { diff --git a/src/structures/BusinessContact.js b/src/structures/BusinessContact.js index 2f85c64..80fd8d6 100644 --- a/src/structures/BusinessContact.js +++ b/src/structures/BusinessContact.js @@ -8,6 +8,9 @@ const Contact = require('./Contact'); */ class BusinessContact extends Contact { _patch(data) { + /** + * The contact's business profile + */ this.businessProfile = data.businessProfile; return super._patch(data); diff --git a/src/structures/Chat.js b/src/structures/Chat.js index b20e0cb..834fb5b 100644 --- a/src/structures/Chat.js +++ b/src/structures/Chat.js @@ -14,12 +14,40 @@ class Chat extends Base { } _patch(data) { + /** + * ID that represents the chat + * @type {object} + */ this.id = data.id; + /** + * Title of the chat + * @type {string} + */ this.name = data.formattedTitle; + + /** + * Indicates if the Chat is a Group Chat + * @type {boolean} + */ this.isGroup = data.isGroup; + + /** + * Indicates if the Chat is readonly + * @type {boolean} + */ this.isReadOnly = data.isReadOnly; + + /** + * Amount of messages unread + * @type {number} + */ this.unreadCount = data.unreadCount; + + /** + * Unix timestamp for when the chat was created + * @type {number} + */ this.timestamp = data.t; return super._patch(data); @@ -29,6 +57,7 @@ class Chat extends Base { * Send a message to this chat * @param {string|MessageMedia} content * @param {object} options + * @returns {Promise} Message that was just sent */ async sendMessage(content, options) { return this.client.sendMessage(this.id._serialized, content, options); diff --git a/src/structures/ClientInfo.js b/src/structures/ClientInfo.js index dfe6432..af1f618 100644 --- a/src/structures/ClientInfo.js +++ b/src/structures/ClientInfo.js @@ -14,9 +14,33 @@ class ClientInfo extends Base { } _patch(data) { + /** + * Name configured to be shown in push notifications + * @type {string} + */ this.pushname = data.pushname; + + /** + * Current user ID + * @type {object} + */ this.me = data.me; + + /** + * Information about the phone this client is connected to + * @type {object} + * @property {string} wa_version WhatsApp Version running on the phone + * @property {string} os_version OS Version running on the phone (iOS or Android version) + * @property {string} device_manufacturer Device manufacturer + * @property {string} device_model Device model + * @property {string} os_build_number OS build number + */ this.phone = data.phone; + + /** + * Platform the phone is running on + * @type {string} + */ this.platform = data.platform; return super._patch(data); diff --git a/src/structures/Contact.js b/src/structures/Contact.js index bff983e..3b4b4b5 100644 --- a/src/structures/Contact.js +++ b/src/structures/Contact.js @@ -14,14 +14,46 @@ class Contact extends Base { } _patch(data) { + /** + * ID that represents the contact + * @type {object} + */ this.id = data.id; + + /** + * Indicates if the contact is a business contact + * @type {boolean} + */ this.isBusiness = data.isBusiness; + + /** + * Indicates if the contact is an enterprise contact + * @type {boolean} + */ this.isEnterprise = data.isEnterprise; + this.labels = data.labels; + + /** + * The contact's name, as saved by the current user + * @type {?string} + */ this.name = data.name; + + /** + * The name that the contact has configured to be shown publically + * @type {string} + */ this.pushname = data.pushname; + this.sectionHeader = data.sectionHeader; + + /** + * A shortened version of name + * @type {?string} + */ this.shortName = data.shortName; + this.statusMute = data.statusMute; this.type = data.type; this.verifiedLevel = data.verifiedLevel; diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 925636d..055e05f 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -22,6 +22,7 @@ class GroupChat extends Chat { /** * Gets the date at which the group was created + * @type {date} */ get createdAt() { return new Date(this.groupMetadata.creation * 1000); @@ -29,12 +30,14 @@ class GroupChat extends Chat { /** * Gets the group description + * @type {string} */ get description() { return this.groupMetadata.desc; } /** * Gets the group participants + * @type {array} */ get participants() { return this.groupMetadata.participants; @@ -42,7 +45,7 @@ class GroupChat extends Chat { /** * Adds a list of participants by ID to the group - * @param {Array[string]} participantIds + * @param {Array} participantIds */ async addParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { @@ -52,7 +55,7 @@ class GroupChat extends Chat { /** * Removes a list of participants by ID to the group - * @param {Array[string]} participantIds + * @param {Array} participantIds */ async removeParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { @@ -62,7 +65,7 @@ class GroupChat extends Chat { /** * Promotes participants by IDs to admins - * @param {Array[string]} participantIds + * @param {Array} participantIds */ async promoteParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { @@ -72,7 +75,7 @@ class GroupChat extends Chat { /** * Demotes participants by IDs to regular users - * @param {Array[string]} participantIds + * @param {Array} participantIds */ async demoteParticipants(participantIds) { return await this.client.pupPage.evaluate((chatId, participantIds) => { @@ -136,6 +139,7 @@ class GroupChat extends Chat { /** * Returns an object with information about the invite code's group * @param {string} inviteCode + * @returns {Promise} Invite information */ static async getInviteInfo(inviteCode) { return await this.client.pupPage.evaluate(inviteCode => { diff --git a/src/structures/Message.js b/src/structures/Message.js index c58c14e..aee1315 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -15,17 +15,79 @@ class Message extends Base { } _patch(data) { + /** + * ID that represents the message + * @type {object} + */ this.id = data.id; + + /** + * Indicates if the message has media available for download + * @type {boolean} + */ this.hasMedia = data.clientUrl ? true : false; + + /** + * Message content + * @type {string} + */ this.body = this.hasMedia ? data.caption || '' : data.body || ''; + + /** + * Message type + * @type {MessageTypes} + */ this.type = data.type; + + /** + * Unix timestamp for when the message was created + * @type {number} + */ this.timestamp = data.t; + + /** + * ID for the Chat that this message was sent to, except if the message was sent by the current user. + * @type {string} + */ this.from = typeof (data.from) === 'object' ? data.from._serialized : data.from; + + /** + * ID for who this message is for. + * + * If the message is sent by the current user, it will be the Chat to which the message is being sent. + * If the message is sent by another user, it will be the ID for the current user. + * @type {string} + */ this.to = typeof (data.to) === 'object' ? data.to._serialized : data.to; + + /** + * If the message was sent to a group, this field will contain the user that sent the message. + * @type {string} + */ this.author = typeof (data.author) === 'object' ? data.author._serialized : data.author; + + /** + * Indicates if the message was forwarded + * @type {boolean} + */ this.isForwarded = data.isForwarded; + + /** + * Indicates if the message was a broadcast + * @type {boolean} + */ this.broadcast = data.broadcast; + + /** + * Indicates if the message was sent by the current user + * @type {boolean} + */ this.fromMe = data.id.fromMe; + + /** + * Indicates if the message was sent as a reply to another message. + * @type {boolean} + */ this.hasQuotedMsg = data.quotedMsg ? true : false; return super._patch(data); @@ -37,7 +99,7 @@ class Message extends Base { /** * Returns the Chat this message was sent in - * @returns {Chat} + * @returns {Promise} */ getChat() { return this.client.getChatById(this._getChatId()); @@ -45,7 +107,7 @@ class Message extends Base { /** * Returns the Contact this message was sent from - * @returns {Contact} + * @returns {Promise} */ getContact() { return this.client.getContactById(this._getChatId()); @@ -53,7 +115,7 @@ class Message extends Base { /** * Returns the quoted message, if any - * @returns {Message} + * @returns {Promise} */ async getQuotedMessage() { if (!this.hasQuotedMsg) return undefined; @@ -67,14 +129,14 @@ class Message extends Base { } /** - * Sends a message as a reply. If chatId is specified, it will be sent + * Sends a message as a reply to this message. If chatId is specified, it will be sent * through the specified Chat. If not, it will send the message * in the same Chat as the original message was sent. * * @param {string|MessageMedia} content * @param {?string} chatId * @param {object} options - * @returns {Message} + * @returns {Promise} */ async reply(content, chatId, options={}) { if (!chatId) { @@ -91,7 +153,7 @@ class Message extends Base { /** * Downloads and returns the attatched message media - * @returns {MessageMedia} + * @returns {Promise} */ async downloadMedia() { if (!this.hasMedia) { diff --git a/src/util/Constants.js b/src/util/Constants.js index 0a08d43..df49d34 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -11,12 +11,22 @@ exports.DefaultOptions = { session: false }; +/** + * Client status + * @readonly + * @enum {number} + */ exports.Status = { INITIALIZING: 0, AUTHENTICATING: 1, READY: 3 }; +/** + * Events that can be emitted by the client + * @readonly + * @enum {string} + */ exports.Events = { AUTHENTICATED: 'authenticated', AUTHENTICATION_FAILURE: 'auth_failure', @@ -29,6 +39,11 @@ exports.Events = { DISCONNECTED: 'disconnected' }; +/** + * Message types + * @readonly + * @enum {string} + */ exports.MessageTypes = { TEXT: 'chat', AUDIO: 'audio', @@ -39,12 +54,22 @@ exports.MessageTypes = { STICKER: 'sticker' }; +/** + * Chat types + * @readonly + * @enum {string} + */ exports.ChatTypes = { SOLO: 'solo', GROUP: 'group', UNKNOWN: 'unknown' }; +/** + * WhatsApp state + * @readonly + * @enum {string} + */ exports.WAState = { CONFLICT: 'CONFLICT', CONNECTED: 'CONNECTED', diff --git a/src/util/Injected.js b/src/util/Injected.js index 2e86302..59c46de 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -1,8 +1,6 @@ 'use strict'; -/** - * Exposes the internal Store to the WhatsApp Web client - */ +// Exposes the internal Store to the WhatsApp Web client exports.ExposeStore = (moduleRaidStr) => { eval('var moduleRaid = ' + moduleRaidStr); // eslint-disable-next-line no-undef