├── docs ├── CNAME ├── scripts │ ├── linenumber.js │ ├── scrollanchor.js │ ├── lang-css.js │ └── jsdoc-toc.js ├── structures_PrivateChat.js.html ├── structures_PrivateContact.js.html ├── authStrategies_NoAuth.js.html ├── structures_Base.js.html ├── structures_BusinessContact.js.html ├── Base.html ├── authStrategies_BaseAuthStrategy.js.html ├── NoAuth.html ├── structures_ProductMetadata.js.html ├── BaseAuthStrategy.html ├── structures_Location.js.html ├── structures_Label.js.html ├── structures_Order.js.html ├── structures_Call.js.html ├── structures_Product.js.html ├── authStrategies_LocalAuth.js.html ├── structures_Payment.js.html ├── structures_ClientInfo.js.html ├── Order.html ├── structures_Buttons.js.html ├── structures_List.js.html ├── authStrategies_LegacySessionAuth.js.html ├── structures_GroupNotification.js.html ├── LocalAuth.html ├── util_InterfaceController.js.html ├── Product.html ├── structures_MessageMedia.js.html ├── Location.html └── util_Constants.js.html ├── tools ├── version-checker │ ├── .version │ └── update-version └── changelog.sh ├── .env.example ├── .editorconfig ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml ├── workflows │ ├── lint.yml │ └── update.yml └── stale.yml ├── .npmignore ├── src ├── structures │ ├── PrivateChat.js │ ├── PrivateContact.js │ ├── BusinessContact.js │ ├── Base.js │ ├── ProductMetadata.js │ ├── Location.js │ ├── index.js │ ├── Label.js │ ├── Order.js │ ├── Call.js │ ├── Product.js │ ├── Payment.js │ ├── ClientInfo.js │ ├── List.js │ ├── Buttons.js │ ├── GroupNotification.js │ ├── MessageMedia.js │ └── Contact.js ├── authStrategies │ ├── NoAuth.js │ ├── BaseAuthStrategy.js │ ├── LocalAuth.js │ └── LegacySessionAuth.js ├── factories │ ├── ChatFactory.js │ └── ContactFactory.js └── util │ ├── InterfaceController.js │ ├── Constants.js │ └── Util.js ├── .jsdoc.json ├── .eslintrc.json ├── shell.js ├── tests ├── README.md ├── helper.js └── structures │ ├── message.js │ └── chat.js ├── index.js ├── .gitignore ├── package.json ├── README.md └── CODE_OF_CONDUCT.md /docs/CNAME: -------------------------------------------------------------------------------- 1 | docs.wwebjs.dev -------------------------------------------------------------------------------- /tools/version-checker/.version: -------------------------------------------------------------------------------- 1 | 2.2212.8 -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | WWEBJS_TEST_REMOTE_ID=XXXXXXXXXX@c.us 2 | WWEBJS_TEST_CLIENT_ID=authenticated 3 | WWEBJS_TEST_MD=1 -------------------------------------------------------------------------------- /tools/changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LAST_TAG=$(git describe --tags --abbrev=0) 4 | git log --pretty="%h - %s" "$LAST_TAG"..HEAD -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = false -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | open-pull-requests-limit: 10 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Official WWebJS Discord Server 4 | url: https://discord.gg/H7DqQs4 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs/* 2 | .github/* 3 | 4 | .eslintrc.json 5 | .jsdoc.json 6 | .editorconfig 7 | 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | *session.json 15 | .wwebjs_auth/ 16 | 17 | .env 18 | tools/ 19 | tests/ 20 | -------------------------------------------------------------------------------- /src/structures/PrivateChat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Chat = require('./Chat'); 4 | 5 | /** 6 | * Represents a Private Chat on WhatsApp 7 | * @extends {Chat} 8 | */ 9 | class PrivateChat extends Chat { 10 | 11 | } 12 | 13 | module.exports = PrivateChat; -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | !function(){var n,e=0,t=document.getElementsByClassName("prettyprint");t&&t[0]&&(n=(n=(t=t[0].getElementsByTagName("code")[0]).innerHTML.split("\n")).map(function(n){return''+n}),t.innerHTML=n.join("\n"))}(); -------------------------------------------------------------------------------- /src/structures/PrivateContact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Contact = require('./Contact'); 4 | 5 | /** 6 | * Represents a Private Contact on WhatsApp 7 | * @extends {Contact} 8 | */ 9 | class PrivateContact extends Contact { 10 | 11 | } 12 | 13 | module.exports = PrivateContact; -------------------------------------------------------------------------------- /src/authStrategies/NoAuth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseAuthStrategy = require('./BaseAuthStrategy'); 4 | 5 | /** 6 | * No session restoring functionality 7 | * Will need to authenticate via QR code every time 8 | */ 9 | class NoAuth extends BaseAuthStrategy { } 10 | 11 | 12 | module.exports = NoAuth; -------------------------------------------------------------------------------- /docs/scripts/scrollanchor.js: -------------------------------------------------------------------------------- 1 | !function(){function o(n){var o,t,e=document.getElementById(n.replace(/^#/,""));e&&(t=e.getBoundingClientRect(),o=t.top+window.pageYOffset,setTimeout(function(){window.scrollTo(0,o-50)},5))}window.addEventListener("load",function(){var n=window.location.hash;n&&"#"!==n&&o(n),window.addEventListener("hashchange",function(){o(window.location.hash)})})}(); -------------------------------------------------------------------------------- /src/factories/ChatFactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const PrivateChat = require('../structures/PrivateChat'); 4 | const GroupChat = require('../structures/GroupChat'); 5 | 6 | class ChatFactory { 7 | static create(client, data) { 8 | if(data.isGroup) { 9 | return new GroupChat(client, data); 10 | } 11 | 12 | return new PrivateChat(client, data); 13 | } 14 | } 15 | 16 | module.exports = ChatFactory; -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | eslint: 9 | name: ESLint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install node v14 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: '14' 17 | - name: Install dependencies 18 | run: npm install 19 | - name: Run ESLint 20 | run: ./node_modules/.bin/eslint . 21 | -------------------------------------------------------------------------------- /src/factories/ContactFactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const PrivateContact = require('../structures/PrivateContact'); 4 | const BusinessContact = require('../structures/BusinessContact'); 5 | 6 | class ContactFactory { 7 | static create(client, data) { 8 | if(data.isBusiness) { 9 | return new BusinessContact(client, data); 10 | } 11 | 12 | return new PrivateContact(client, data); 13 | } 14 | } 15 | 16 | module.exports = ContactFactory; -------------------------------------------------------------------------------- /src/structures/BusinessContact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Contact = require('./Contact'); 4 | 5 | /** 6 | * Represents a Business Contact on WhatsApp 7 | * @extends {Contact} 8 | */ 9 | class BusinessContact extends Contact { 10 | _patch(data) { 11 | /** 12 | * The contact's business profile 13 | */ 14 | this.businessProfile = data.businessProfile; 15 | 16 | return super._patch(data); 17 | } 18 | 19 | } 20 | 21 | module.exports = BusinessContact; -------------------------------------------------------------------------------- /src/structures/Base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Represents a WhatsApp data structure 5 | */ 6 | class Base { 7 | constructor(client) { 8 | /** 9 | * The client that instantiated this 10 | * @readonly 11 | */ 12 | Object.defineProperty(this, 'client', { value: client }); 13 | } 14 | 15 | _clone() { 16 | return Object.assign(Object.create(this), this); 17 | } 18 | 19 | _patch(data) { return data; } 20 | } 21 | 22 | module.exports = Base; -------------------------------------------------------------------------------- /src/authStrategies/BaseAuthStrategy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Base class which all authentication strategies extend 5 | */ 6 | class BaseAuthStrategy { 7 | constructor() {} 8 | setup(client) { 9 | this.client = client; 10 | } 11 | async beforeBrowserInitialized() {} 12 | async afterBrowserInitialized() {} 13 | async onAuthenticationNeeded() { 14 | return { 15 | failed: false, 16 | restart: false, 17 | failureEventPayload: undefined 18 | }; 19 | } 20 | async getAuthEventPayload() {} 21 | async logout() {} 22 | } 23 | 24 | module.exports = BaseAuthStrategy; -------------------------------------------------------------------------------- /src/structures/ProductMetadata.js: -------------------------------------------------------------------------------- 1 | const Base = require('./Base'); 2 | 3 | class ProductMetadata extends Base { 4 | constructor(client, data) { 5 | super(client); 6 | 7 | if (data) this._patch(data); 8 | } 9 | 10 | _patch(data) { 11 | /** Product ID */ 12 | this.id = data.id; 13 | /** Retailer ID */ 14 | this.retailer_id = data.retailer_id; 15 | /** Product Name */ 16 | this.name = data.name; 17 | /** Product Description */ 18 | this.description = data.description; 19 | 20 | return super._patch(data); 21 | } 22 | 23 | } 24 | 25 | module.exports = ProductMetadata; -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc"] 5 | }, 6 | "source": { 7 | "include": ["src", "package.json", "README.md"], 8 | "includePattern": ".js$", 9 | "excludePattern": "(node_modules/|docs)" 10 | }, 11 | "plugins": [ 12 | "plugins/markdown" 13 | ], 14 | "templates": { 15 | "cleverLinks": false, 16 | "monospaceLinks": true, 17 | "useLongnameInNav": false, 18 | "showInheritedInNav": true 19 | }, 20 | "opts": { 21 | "destination": "./docs/", 22 | "encoding": "utf8", 23 | "recurse": true, 24 | "template": "./node_modules/jsdoc-baseline" 25 | } 26 | } -------------------------------------------------------------------------------- /src/structures/Location.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Location information 5 | */ 6 | class Location { 7 | /** 8 | * @param {number} latitude 9 | * @param {number} longitude 10 | * @param {?string} description 11 | */ 12 | constructor(latitude, longitude, description) { 13 | /** 14 | * Location latitude 15 | * @type {number} 16 | */ 17 | this.latitude = latitude; 18 | 19 | /** 20 | * Location longitude 21 | * @type {number} 22 | */ 23 | this.longitude = longitude; 24 | 25 | /** 26 | * Name for the location 27 | * @type {?string} 28 | */ 29 | this.description = description; 30 | } 31 | } 32 | 33 | module.exports = Location; -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - enhancement 10 | - bug 11 | - WhatsApp Change 12 | # Label to use when marking an issue as stale 13 | staleLabel: stale 14 | # Comment to post when marking an issue as stale. Set to `false` to disable 15 | markComment: > 16 | This issue has been automatically marked as stale because it has not had 17 | recent activity. It will be closed if no further activity occurs. Thank you 18 | for your contributions. 19 | # Comment to post when closing a stale issue. Set to `false` to disable 20 | closeComment: false 21 | -------------------------------------------------------------------------------- /src/structures/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Base: require('./Base'), 3 | BusinessContact: require('./BusinessContact'), 4 | Chat: require('./Chat'), 5 | ClientInfo: require('./ClientInfo'), 6 | Contact: require('./Contact'), 7 | GroupChat: require('./GroupChat'), 8 | Location: require('./Location'), 9 | Message: require('./Message'), 10 | MessageMedia: require('./MessageMedia'), 11 | PrivateChat: require('./PrivateChat'), 12 | PrivateContact: require('./PrivateContact'), 13 | GroupNotification: require('./GroupNotification'), 14 | Label: require('./Label.js'), 15 | Order: require('./Order'), 16 | Product: require('./Product'), 17 | Call: require('./Call'), 18 | Buttons: require('./Buttons'), 19 | List: require('./List'), 20 | Payment: require('./Payment') 21 | }; 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "extends": ["eslint:recommended", "plugin:mocha/recommended"], 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2020 15 | }, 16 | "plugins": ["mocha"], 17 | "ignorePatterns": ["docs"], 18 | "rules": { 19 | "indent": [ 20 | "error", 21 | 4 22 | ], 23 | "linebreak-style": [ 24 | "error", 25 | "unix" 26 | ], 27 | "quotes": [ 28 | "error", 29 | "single" 30 | ], 31 | "semi": [ 32 | "error", 33 | "always" 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /shell.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ==== wwebjs-shell ==== 3 | * Used for quickly testing library features 4 | * 5 | * Running `npm run shell` will start WhatsApp Web with headless=false 6 | * and then drop you into Node REPL with `client` in its context. 7 | */ 8 | 9 | const repl = require('repl'); 10 | 11 | const { Client, LocalAuth } = require('./index'); 12 | 13 | const client = new Client({ 14 | puppeteer: { headless: false }, 15 | authStrategy: new LocalAuth() 16 | }); 17 | 18 | console.log('Initializing...'); 19 | 20 | client.initialize(); 21 | 22 | client.on('qr', () => { 23 | console.log('Please scan the QR code on the browser.'); 24 | }); 25 | 26 | client.on('authenticated', (session) => { 27 | console.log(JSON.stringify(session)); 28 | }); 29 | 30 | client.on('ready', () => { 31 | const shell = repl.start('wwebjs> '); 32 | shell.context.client = client; 33 | shell.on('exit', async () => { 34 | await client.destroy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /docs/scripts/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']+)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}\b/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]),PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]),PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## Running tests 2 | 3 | These tests require an authenticated WhatsApp Web session, as well as an additional phone that you can send messages to. 4 | 5 | This can be configured using the following environment variables: 6 | - `WWEBJS_TEST_SESSION`: A JSON-formatted string with legacy auth session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. 7 | - `WWEBJS_TEST_SESSION_PATH`: Path to a JSON file that contains the legacy auth session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. 8 | - `WWEBJS_TEST_CLIENT_ID`: `clientId` to use for local file based authentication. 9 | - `WWEBJS_TEST_REMOTE_ID`: A valid WhatsApp ID that you can send messages to, e.g. `123456789@c.us`. It should be different from the ID used by the provided session. 10 | 11 | You *must* set `WWEBJS_TEST_REMOTE_ID` **and** either `WWEBJS_TEST_SESSION`, `WWEBJS_TEST_SESSION_PATH` or `WWEBJS_TEST_CLIENT_ID` for the tests to run properly. 12 | 13 | ### Multidevice 14 | Some of the tested functionality depends on whether the account has multidevice enabled or not. If you are using multidevice, you should set `WWEBJS_TEST_MD=1`. -------------------------------------------------------------------------------- /src/structures/Label.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Base = require('./Base'); 4 | // eslint-disable-next-line no-unused-vars 5 | const Chat = require('./Chat'); 6 | 7 | /** 8 | * WhatsApp Business Label information 9 | */ 10 | class Label extends Base { 11 | /** 12 | * @param {Base} client 13 | * @param {object} labelData 14 | */ 15 | constructor(client, labelData){ 16 | super(client); 17 | 18 | if(labelData) this._patch(labelData); 19 | } 20 | 21 | _patch(labelData){ 22 | /** 23 | * Label ID 24 | * @type {string} 25 | */ 26 | this.id = labelData.id; 27 | 28 | /** 29 | * Label name 30 | * @type {string} 31 | */ 32 | this.name = labelData.name; 33 | 34 | /** 35 | * Label hex color 36 | * @type {string} 37 | */ 38 | this.hexColor = labelData.hexColor; 39 | } 40 | /** 41 | * Get all chats that have been assigned this Label 42 | * @returns {Promise>} 43 | */ 44 | async getChats(){ 45 | return this.client.getChatsByLabelId(this.id); 46 | } 47 | 48 | } 49 | 50 | module.exports = Label; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Constants = require('./src/util/Constants'); 4 | 5 | module.exports = { 6 | Client: require('./src/Client'), 7 | 8 | version: require('./package.json').version, 9 | 10 | // Structures 11 | Chat: require('./src/structures/Chat'), 12 | PrivateChat: require('./src/structures/PrivateChat'), 13 | GroupChat: require('./src/structures/GroupChat'), 14 | Message: require('./src/structures/Message'), 15 | MessageMedia: require('./src/structures/MessageMedia'), 16 | Contact: require('./src/structures/Contact'), 17 | PrivateContact: require('./src/structures/PrivateContact'), 18 | BusinessContact: require('./src/structures/BusinessContact'), 19 | ClientInfo: require('./src/structures/ClientInfo'), 20 | Location: require('./src/structures/Location'), 21 | ProductMetadata: require('./src/structures/ProductMetadata'), 22 | List: require('./src/structures/List'), 23 | Buttons: require('./src/structures/Buttons'), 24 | 25 | // Auth Strategies 26 | NoAuth: require('./src/authStrategies/NoAuth'), 27 | LocalAuth: require('./src/authStrategies/LocalAuth'), 28 | LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'), 29 | 30 | ...Constants 31 | }; 32 | -------------------------------------------------------------------------------- /src/structures/Order.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Base = require('./Base'); 4 | const Product = require('./Product'); 5 | 6 | /** 7 | * Represents a Order on WhatsApp 8 | * @extends {Base} 9 | */ 10 | class Order extends Base { 11 | constructor(client, data) { 12 | super(client); 13 | 14 | if (data) this._patch(data); 15 | } 16 | 17 | _patch(data) { 18 | /** 19 | * List of products 20 | * @type {Array} 21 | */ 22 | if (data.products) { 23 | this.products = data.products.map(product => new Product(this.client, product)); 24 | } 25 | /** 26 | * Order Subtotal 27 | * @type {string} 28 | */ 29 | this.subtotal = data.subtotal; 30 | /** 31 | * Order Total 32 | * @type {string} 33 | */ 34 | this.total = data.total; 35 | /** 36 | * Order Currency 37 | * @type {string} 38 | */ 39 | this.currency = data.currency; 40 | /** 41 | * Order Created At 42 | * @type {number} 43 | */ 44 | this.createdAt = data.createdAt; 45 | 46 | return super._patch(data); 47 | } 48 | 49 | 50 | } 51 | 52 | module.exports = Order; -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Update 2 | 3 | on: 4 | schedule: 5 | - cron: "0/15 * * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update: 10 | runs-on: ubuntu-latest 11 | defaults: 12 | run: 13 | working-directory: ./tools/version-checker 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Install node v16 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: '16' 21 | - name: Install dependencies 22 | run: npm install 23 | - name: Run Updater 24 | run: ./update-version 25 | - name: Store WA Version 26 | run: echo WA_VERSION=`cat ./.version` >> $GITHUB_ENV 27 | - name: Create Pull Request 28 | uses: peter-evans/create-pull-request@v3 29 | with: 30 | branch: auto-wa-web-update/patch 31 | delete-branch: true 32 | commit-message: Update supported WhatsApp Web version to v${{ env.WA_VERSION }} 33 | title: Update WhatsApp Web Version (${{ env.WA_VERSION }}) 34 | body: | 35 | A new version of WhatsApp Web has been detected! 36 | 37 | Tests should be run against this new version before merging. 38 | labels: WhatsApp Change 39 | reviewers: pedroslopez 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Lock files 40 | package-lock.json 41 | yarn.lock 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | # next.js build output 65 | .next 66 | 67 | # macOS 68 | ._* 69 | .DS_Store 70 | 71 | # Test sessions 72 | *session.json 73 | .wwebjs_auth/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature request 2 | description: Suggest an idea for this project 3 | labels: enhancement 4 | 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue related to this feature request already exists. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | 14 | - type: textarea 15 | attributes: 16 | label: Is your feature request related to a problem? Please describe. 17 | description: A concise description of the problem you are facing or the motivetion behind this feature request. 18 | placeholder: I faced a problem due to ... 19 | validations: 20 | required: false 21 | 22 | - type: textarea 23 | attributes: 24 | label: Describe the solution you'd like. 25 | description: A concise description of the solution for the issue. 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | attributes: 31 | label: Describe an alternate solution. 32 | description: Is there any other approach to solve the problem? 33 | validations: 34 | required: false 35 | 36 | - type: textarea 37 | attributes: 38 | label: Additional context 39 | description: | 40 | Links? Screenshots? References? Anything that will give us more context about what you would like to see! 41 | validations: 42 | required: false 43 | -------------------------------------------------------------------------------- /tools/version-checker/update-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const fetch = require('node-fetch'); 5 | 6 | const getLatestVersion = async (currentVersion) => { 7 | const res = await fetch(`https://web.whatsapp.com/check-update?version=${currentVersion}&platform=web`); 8 | const data = await res.json(); 9 | return data.currentVersion; 10 | }; 11 | 12 | const getCurrentVersion = () => { 13 | try { 14 | const versionFile = fs.readFileSync('./.version'); 15 | return versionFile ? versionFile.toString().trim() : null; 16 | } catch(_) { 17 | return null; 18 | } 19 | }; 20 | 21 | const updateVersion = async (oldVersion, newVersion) => { 22 | const readmePath = '../../README.md'; 23 | 24 | const readme = fs.readFileSync(readmePath); 25 | const newReadme = readme.toString().replaceAll(oldVersion, newVersion); 26 | 27 | fs.writeFileSync(readmePath, newReadme); 28 | fs.writeFileSync('./.version', newVersion); 29 | 30 | }; 31 | 32 | (async () => { 33 | const currentVersion = getCurrentVersion(); 34 | const version = await getLatestVersion(currentVersion); 35 | 36 | console.log(`Current version: ${currentVersion}`); 37 | console.log(`Latest version: ${version}`); 38 | 39 | if(currentVersion !== version) { 40 | console.log('Updating files...'); 41 | await updateVersion(currentVersion, version); 42 | console.log('Updated!'); 43 | } else { 44 | console.log('No changes.'); 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whatsapp-web.js", 3 | "version": "1.16.6", 4 | "description": "Library for interacting with the WhatsApp Web API ", 5 | "main": "./index.js", 6 | "typings": "./index.d.ts", 7 | "scripts": { 8 | "test": "mocha tests --recursive --timeout 5000", 9 | "test-single": "mocha", 10 | "shell": "node --experimental-repl-await ./shell.js", 11 | "generate-docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/pedroslopez/whatsapp-web.js.git" 16 | }, 17 | "keywords": [ 18 | "whatsapp", 19 | "whatsapp-web", 20 | "api", 21 | "bot", 22 | "client", 23 | "node" 24 | ], 25 | "author": "Pedro Lopez", 26 | "license": "Apache-2.0", 27 | "bugs": { 28 | "url": "https://github.com/pedroslopez/whatsapp-web.js/issues" 29 | }, 30 | "homepage": "https://wwebjs.dev/", 31 | "dependencies": { 32 | "@pedroslopez/moduleraid": "^5.0.2", 33 | "fluent-ffmpeg": "^2.1.2", 34 | "jsqr": "^1.3.1", 35 | "mime": "^3.0.0", 36 | "node-fetch": "^2.6.5", 37 | "node-webpmux": "^3.1.0", 38 | "puppeteer": "^13.0.0" 39 | }, 40 | "devDependencies": { 41 | "@types/node-fetch": "^2.5.12", 42 | "chai": "^4.3.4", 43 | "chai-as-promised": "^7.1.1", 44 | "dotenv": "^16.0.0", 45 | "eslint": "^8.4.1", 46 | "eslint-plugin-mocha": "^10.0.3", 47 | "jsdoc": "^3.6.4", 48 | "jsdoc-baseline": "^0.1.5", 49 | "mocha": "^9.0.2", 50 | "sinon": "^13.0.1" 51 | }, 52 | "engines": { 53 | "node": ">=12.0.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/structures/Call.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Base = require('./Base'); 4 | 5 | /** 6 | * Represents a Call on WhatsApp 7 | * @extends {Base} 8 | */ 9 | class Call extends Base { 10 | constructor(client, data) { 11 | super(client); 12 | 13 | if (data) this._patch(data); 14 | } 15 | 16 | _patch(data) { 17 | /** 18 | * Call ID 19 | * @type {string} 20 | */ 21 | this.id = data.id; 22 | /** 23 | * From 24 | * @type {string} 25 | */ 26 | this.from = data.peerJid; 27 | /** 28 | * Unix timestamp for when the call was created 29 | * @type {number} 30 | */ 31 | this.timestamp = data.offerTime; 32 | /** 33 | * Is video 34 | * @type {boolean} 35 | */ 36 | this.isVideo = data.isVideo; 37 | /** 38 | * Is Group 39 | * @type {boolean} 40 | */ 41 | this.isGroup = data.isGroup; 42 | /** 43 | * Indicates if the call was sent by the current user 44 | * @type {boolean} 45 | */ 46 | this.fromMe = data.outgoing; 47 | /** 48 | * Indicates if the call can be handled in waweb 49 | * @type {boolean} 50 | */ 51 | this.canHandleLocally = data.canHandleLocally; 52 | /** 53 | * Indicates if the call Should be handled in waweb 54 | * @type {boolean} 55 | */ 56 | this.webClientShouldHandle = data.webClientShouldHandle; 57 | /** 58 | * Object with participants 59 | * @type {object} 60 | */ 61 | this.participants = data.participants; 62 | 63 | return super._patch(data); 64 | } 65 | 66 | } 67 | 68 | module.exports = Call; -------------------------------------------------------------------------------- /src/structures/Product.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Base = require('./Base'); 4 | const ProductMetadata = require('./ProductMetadata'); 5 | 6 | /** 7 | * Represents a Product on WhatsAppBusiness 8 | * @extends {Base} 9 | */ 10 | class Product extends Base { 11 | constructor(client, data) { 12 | super(client); 13 | 14 | if (data) this._patch(data); 15 | } 16 | 17 | _patch(data) { 18 | /** 19 | * Product ID 20 | * @type {string} 21 | */ 22 | this.id = data.id; 23 | /** 24 | * Price 25 | * @type {string} 26 | */ 27 | this.price = data.price ? data.price : ''; 28 | /** 29 | * Product Thumbnail 30 | * @type {string} 31 | */ 32 | this.thumbnailUrl = data.thumbnailUrl; 33 | /** 34 | * Currency 35 | * @type {string} 36 | */ 37 | this.currency = data.currency; 38 | /** 39 | * Product Name 40 | * @type {string} 41 | */ 42 | this.name = data.name; 43 | /** 44 | * Product Quantity 45 | * @type {number} 46 | */ 47 | this.quantity = data.quantity; 48 | /** Product metadata */ 49 | this.data = null; 50 | return super._patch(data); 51 | } 52 | 53 | async getData() { 54 | if (this.data === null) { 55 | let result = await this.client.pupPage.evaluate((productId) => { 56 | return window.WWebJS.getProductMetadata(productId); 57 | }, this.id); 58 | if (!result) { 59 | this.data = undefined; 60 | } else { 61 | this.data = new ProductMetadata(this.client, result); 62 | } 63 | } 64 | return this.data; 65 | } 66 | } 67 | 68 | module.exports = Product; -------------------------------------------------------------------------------- /src/authStrategies/LocalAuth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const BaseAuthStrategy = require('./BaseAuthStrategy'); 6 | 7 | /** 8 | * Local directory-based authentication 9 | * @param {object} options - options 10 | * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance 11 | * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" 12 | */ 13 | class LocalAuth extends BaseAuthStrategy { 14 | constructor({ clientId, dataPath }={}) { 15 | super(); 16 | 17 | const idRegex = /^[-_\w]+$/i; 18 | if(clientId && !idRegex.test(clientId)) { 19 | throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.'); 20 | } 21 | 22 | this.dataPath = path.resolve(dataPath || './.wwebjs_auth/'); 23 | this.clientId = clientId; 24 | } 25 | 26 | async beforeBrowserInitialized() { 27 | const puppeteerOpts = this.client.options.puppeteer; 28 | const sessionDirName = this.clientId ? `session-${this.clientId}` : 'session'; 29 | const dirPath = path.join(this.dataPath, sessionDirName); 30 | 31 | if(puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) { 32 | throw new Error('LocalAuth is not compatible with a user-supplied userDataDir.'); 33 | } 34 | 35 | fs.mkdirSync(dirPath, { recursive: true }); 36 | 37 | this.client.options.puppeteer = { 38 | ...puppeteerOpts, 39 | userDataDir: dirPath 40 | }; 41 | 42 | this.userDataDir = dirPath; 43 | } 44 | 45 | async logout() { 46 | if (this.userDataDir) { 47 | return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true }); 48 | } 49 | } 50 | 51 | } 52 | 53 | module.exports = LocalAuth; -------------------------------------------------------------------------------- /tests/helper.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { Client, LegacySessionAuth, LocalAuth } = require('..'); 3 | 4 | require('dotenv').config(); 5 | 6 | const remoteId = process.env.WWEBJS_TEST_REMOTE_ID; 7 | if(!remoteId) throw new Error('The WWEBJS_TEST_REMOTE_ID environment variable has not been set.'); 8 | 9 | function isUsingLegacySession() { 10 | return Boolean(process.env.WWEBJS_TEST_SESSION || process.env.WWEBJS_TEST_SESSION_PATH); 11 | } 12 | 13 | function isMD() { 14 | return Boolean(process.env.WWEBJS_TEST_MD); 15 | } 16 | 17 | if(isUsingLegacySession() && isMD()) throw 'Cannot use legacy sessions with WWEBJS_TEST_MD=true'; 18 | 19 | function getSessionFromEnv() { 20 | if (!isUsingLegacySession()) return null; 21 | 22 | const envSession = process.env.WWEBJS_TEST_SESSION; 23 | if(envSession) return JSON.parse(envSession); 24 | 25 | const envSessionPath = process.env.WWEBJS_TEST_SESSION_PATH; 26 | if(envSessionPath) { 27 | const absPath = path.resolve(process.cwd(), envSessionPath); 28 | return require(absPath); 29 | } 30 | } 31 | 32 | function createClient({authenticated, options: additionalOpts}={}) { 33 | const options = {}; 34 | 35 | if(authenticated) { 36 | const legacySession = getSessionFromEnv(); 37 | if(legacySession) { 38 | options.authStrategy = new LegacySessionAuth({ 39 | session: legacySession 40 | }); 41 | } else { 42 | const clientId = process.env.WWEBJS_TEST_CLIENT_ID; 43 | if(!clientId) throw new Error('No session found in environment.'); 44 | options.authStrategy = new LocalAuth({ 45 | clientId 46 | }); 47 | } 48 | } 49 | 50 | const allOpts = {...options, ...(additionalOpts || {})}; 51 | return new Client(allOpts); 52 | } 53 | 54 | function sleep(ms) { 55 | return new Promise(resolve => setTimeout(resolve, ms)); 56 | } 57 | 58 | module.exports = { 59 | sleep, 60 | createClient, 61 | isUsingLegacySession, 62 | isMD, 63 | remoteId, 64 | }; -------------------------------------------------------------------------------- /src/structures/Payment.js: -------------------------------------------------------------------------------- 1 | const Base = require('./Base'); 2 | 3 | class Payment extends Base { 4 | constructor(client, data) { 5 | super(client); 6 | 7 | if (data) this._patch(data); 8 | } 9 | 10 | _patch(data) { 11 | /** 12 | * The payment Id 13 | * @type {object} 14 | */ 15 | this.id = data.id; 16 | 17 | /** 18 | * The payment currency 19 | * @type {string} 20 | */ 21 | this.paymentCurrency = data.paymentCurrency; 22 | 23 | /** 24 | * The payment ammount ( R$ 1.00 = 1000 ) 25 | * @type {number} 26 | */ 27 | this.paymentAmount1000 = data.paymentAmount1000; 28 | 29 | /** 30 | * The payment receiver 31 | * @type {object} 32 | */ 33 | this.paymentMessageReceiverJid = data.paymentMessageReceiverJid; 34 | 35 | /** 36 | * The payment transaction timestamp 37 | * @type {number} 38 | */ 39 | this.paymentTransactionTimestamp = data.paymentTransactionTimestamp; 40 | 41 | /** 42 | * The paymentStatus 43 | * 44 | * Possible Status 45 | * 0:UNKNOWN_STATUS 46 | * 1:PROCESSING 47 | * 2:SENT 48 | * 3:NEED_TO_ACCEPT 49 | * 4:COMPLETE 50 | * 5:COULD_NOT_COMPLETE 51 | * 6:REFUNDED 52 | * 7:EXPIRED 53 | * 8:REJECTED 54 | * 9:CANCELLED 55 | * 10:WAITING_FOR_PAYER 56 | * 11:WAITING 57 | * 58 | * @type {number} 59 | */ 60 | this.paymentStatus = data.paymentStatus; 61 | 62 | /** 63 | * Integer that represents the payment Text 64 | * @type {number} 65 | */ 66 | this.paymentTxnStatus = data.paymentTxnStatus; 67 | 68 | /** 69 | * The note sent with the payment 70 | * @type {string} 71 | */ 72 | this.paymentNote = !data.paymentNoteMsg ? undefined : data.paymentNoteMsg.body ? data.paymentNoteMsg.body : undefined ; 73 | 74 | return super._patch(data); 75 | } 76 | 77 | } 78 | 79 | module.exports = Payment; 80 | -------------------------------------------------------------------------------- /src/structures/ClientInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Base = require('./Base'); 4 | 5 | /** 6 | * Current connection information 7 | * @extends {Base} 8 | */ 9 | class ClientInfo extends Base { 10 | constructor(client, data) { 11 | super(client); 12 | 13 | if (data) this._patch(data); 14 | } 15 | 16 | _patch(data) { 17 | /** 18 | * Name configured to be shown in push notifications 19 | * @type {string} 20 | */ 21 | this.pushname = data.pushname; 22 | 23 | /** 24 | * Current user ID 25 | * @type {object} 26 | */ 27 | this.wid = data.wid; 28 | 29 | /** 30 | * @type {object} 31 | * @deprecated Use .wid instead 32 | */ 33 | this.me = data.wid; 34 | 35 | /** 36 | * Information about the phone this client is connected to. Not available in multi-device. 37 | * @type {object} 38 | * @property {string} wa_version WhatsApp Version running on the phone 39 | * @property {string} os_version OS Version running on the phone (iOS or Android version) 40 | * @property {string} device_manufacturer Device manufacturer 41 | * @property {string} device_model Device model 42 | * @property {string} os_build_number OS build number 43 | * @deprecated 44 | */ 45 | this.phone = data.phone; 46 | 47 | /** 48 | * Platform WhatsApp is running on 49 | * @type {string} 50 | */ 51 | this.platform = data.platform; 52 | 53 | return super._patch(data); 54 | } 55 | 56 | /** 57 | * Get current battery percentage and charging status for the attached device 58 | * @returns {object} batteryStatus 59 | * @returns {number} batteryStatus.battery - The current battery percentage 60 | * @returns {boolean} batteryStatus.plugged - Indicates if the phone is plugged in (true) or not (false) 61 | * @deprecated 62 | */ 63 | async getBatteryStatus() { 64 | return await this.client.pupPage.evaluate(() => { 65 | const { battery, plugged } = window.Store.Conn; 66 | return { battery, plugged }; 67 | }); 68 | } 69 | } 70 | 71 | module.exports = ClientInfo; -------------------------------------------------------------------------------- /docs/structures_PrivateChat.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/PrivateChat.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
33 | 
34 | const Chat = require('./Chat');
35 | 
36 | /**
37 |  * Represents a Private Chat on WhatsApp
38 |  * @extends {Chat}
39 |  */
40 | class PrivateChat extends Chat {
41 | 
42 | }
43 | 
44 | module.exports = PrivateChat;
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/structures_PrivateContact.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/PrivateContact.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
33 | 
34 | const Contact = require('./Contact');
35 | 
36 | /**
37 |  * Represents a Private Contact on WhatsApp
38 |  * @extends {Contact}
39 |  */
40 | class PrivateContact extends Contact {
41 | 
42 | }
43 | 
44 | module.exports = PrivateContact;
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/authStrategies_NoAuth.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: authStrategies/NoAuth.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
33 | 
34 | const BaseAuthStrategy = require('./BaseAuthStrategy');
35 | 
36 | /**
37 |  * No session restoring functionality
38 |  * Will need to authenticate via QR code every time
39 | */
40 | class NoAuth extends BaseAuthStrategy { }
41 | 
42 | 
43 | module.exports = NoAuth;
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/scripts/jsdoc-toc.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | // TODO: make the node ID configurable 3 | var treeNode = $('#jsdoc-toc-nav'); 4 | 5 | // initialize the tree 6 | treeNode.tree({ 7 | autoEscape: false, 8 | closedIcon: '⇢', 9 | data: [{"label":"Globals","id":"global","children":[]},{"label":"Base","id":"Base","children":[]},{"label":"BaseAuthStrategy","id":"BaseAuthStrategy","children":[]},{"label":"BusinessContact","id":"BusinessContact","children":[]},{"label":"Buttons","id":"Buttons","children":[]},{"label":"Call","id":"Call","children":[]},{"label":"Chat","id":"Chat","children":[]},{"label":"Client","id":"Client","children":[]},{"label":"ClientInfo","id":"ClientInfo","children":[]},{"label":"Contact","id":"Contact","children":[]},{"label":"GroupChat","id":"GroupChat","children":[]},{"label":"GroupNotification","id":"GroupNotification","children":[]},{"label":"InterfaceController","id":"InterfaceController","children":[]},{"label":"Label","id":"Label","children":[]},{"label":"LegacySessionAuth","id":"LegacySessionAuth","children":[]},{"label":"List","id":"List","children":[]},{"label":"LocalAuth","id":"LocalAuth","children":[]},{"label":"Location","id":"Location","children":[]},{"label":"Message","id":"Message","children":[]},{"label":"MessageMedia","id":"MessageMedia","children":[]},{"label":"NoAuth","id":"NoAuth","children":[]},{"label":"Order","id":"Order","children":[]},{"label":"PrivateChat","id":"PrivateChat","children":[]},{"label":"PrivateContact","id":"PrivateContact","children":[]},{"label":"Product","id":"Product","children":[]},{"label":"Util","id":"Util","children":[]}], 10 | openedIcon: ' ⇣', 11 | saveState: false, 12 | useContextMenu: false 13 | }); 14 | 15 | // add event handlers 16 | // TODO 17 | })(jQuery); 18 | -------------------------------------------------------------------------------- /src/structures/List.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Util = require('../util/Util'); 4 | 5 | /** 6 | * Message type List 7 | */ 8 | class List { 9 | /** 10 | * @param {string} body 11 | * @param {string} buttonText 12 | * @param {Array} sections 13 | * @param {string?} title 14 | * @param {string?} footer 15 | */ 16 | constructor(body, buttonText, sections, title, footer) { 17 | /** 18 | * Message body 19 | * @type {string} 20 | */ 21 | this.description = body; 22 | 23 | /** 24 | * List button text 25 | * @type {string} 26 | */ 27 | this.buttonText = buttonText; 28 | 29 | /** 30 | * title of message 31 | * @type {string} 32 | */ 33 | this.title = title; 34 | 35 | 36 | /** 37 | * footer of message 38 | * @type {string} 39 | */ 40 | this.footer = footer; 41 | 42 | /** 43 | * sections of message 44 | * @type {Array} 45 | */ 46 | this.sections = this._format(sections); 47 | 48 | } 49 | 50 | /** 51 | * Creates section array from simple array 52 | * @param {Array} sections 53 | * @returns {Array} 54 | * @example 55 | * Input: [{title:'sectionTitle',rows:[{id:'customId', title:'ListItem2', description: 'desc'},{title:'ListItem2'}]}}] 56 | * Returns: [{'title':'sectionTitle','rows':[{'rowId':'customId','title':'ListItem1','description':'desc'},{'rowId':'oGSRoD','title':'ListItem2','description':''}]}] 57 | */ 58 | _format(sections){ 59 | if(!sections.length){throw '[LT02] List without sections';} 60 | if(sections.length > 1 && sections.filter(s => typeof s.title == 'undefined').length > 1){throw '[LT05] You can\'t have more than one empty title.';} 61 | return sections.map( (section) =>{ 62 | if(!section.rows.length){throw '[LT03] Section without rows';} 63 | return { 64 | title: section.title ? section.title : undefined, 65 | rows: section.rows.map( (row) => { 66 | if(!row.title){throw '[LT04] Row without title';} 67 | return { 68 | rowId: row.id ? row.id : Util.generateHash(6), 69 | title: row.title, 70 | description: row.description ? row.description : '' 71 | }; 72 | }) 73 | }; 74 | }); 75 | } 76 | 77 | } 78 | 79 | module.exports = List; 80 | -------------------------------------------------------------------------------- /src/structures/Buttons.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MessageMedia = require('./MessageMedia'); 4 | const Util = require('../util/Util'); 5 | 6 | /** 7 | * Button spec used in Buttons constructor 8 | * @typedef {Object} ButtonSpec 9 | * @property {string=} id - Custom ID to set on the button. A random one will be generated if one is not passed. 10 | * @property {string} body - The text to show on the button. 11 | */ 12 | 13 | /** 14 | * @typedef {Object} FormattedButtonSpec 15 | * @property {string} buttonId 16 | * @property {number} type 17 | * @property {Object} buttonText 18 | */ 19 | 20 | /** 21 | * Message type buttons 22 | */ 23 | class Buttons { 24 | /** 25 | * @param {string|MessageMedia} body 26 | * @param {ButtonSpec[]} buttons - See {@link ButtonSpec} 27 | * @param {string?} title 28 | * @param {string?} footer 29 | */ 30 | constructor(body, buttons, title, footer) { 31 | /** 32 | * Message body 33 | * @type {string|MessageMedia} 34 | */ 35 | this.body = body; 36 | 37 | /** 38 | * title of message 39 | * @type {string} 40 | */ 41 | this.title = title; 42 | 43 | /** 44 | * footer of message 45 | * @type {string} 46 | */ 47 | this.footer = footer; 48 | 49 | if (body instanceof MessageMedia) { 50 | this.type = 'media'; 51 | this.title = ''; 52 | }else{ 53 | this.type = 'chat'; 54 | } 55 | 56 | /** 57 | * buttons of message 58 | * @type {FormattedButtonSpec[]} 59 | */ 60 | this.buttons = this._format(buttons); 61 | if(!this.buttons.length){ throw '[BT01] No buttons';} 62 | 63 | } 64 | 65 | /** 66 | * Creates button array from simple array 67 | * @param {ButtonSpec[]} buttons 68 | * @returns {FormattedButtonSpec[]} 69 | * @example 70 | * Input: [{id:'customId',body:'button1'},{body:'button2'},{body:'button3'},{body:'button4'}] 71 | * Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}] 72 | */ 73 | _format(buttons){ 74 | buttons = buttons.slice(0,3); // phone users can only see 3 buttons, so lets limit this 75 | return buttons.map((btn) => { 76 | return {'buttonId':btn.id ? String(btn.id) : Util.generateHash(6),'buttonText':{'displayText':btn.body},'type':1}; 77 | }); 78 | } 79 | 80 | } 81 | 82 | module.exports = Buttons; -------------------------------------------------------------------------------- /docs/structures_Base.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Base.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
33 | 
34 | /**
35 |  * Represents a WhatsApp data structure
36 |  */
37 | class Base {
38 |     constructor(client) {
39 |         /**
40 |          * The client that instantiated this
41 |          * @readonly
42 |          */
43 |         Object.defineProperty(this, 'client', { value: client });
44 |     }
45 | 
46 |     _clone() {
47 |         return Object.assign(Object.create(this), this);
48 |     }
49 |     
50 |     _patch(data) { return data; }
51 | }
52 | 
53 | module.exports = Base;
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/structures_BusinessContact.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/BusinessContact.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
33 | 
34 | const Contact = require('./Contact');
35 | 
36 | /**
37 |  * Represents a Business Contact on WhatsApp
38 |  * @extends {Contact}
39 |  */
40 | class BusinessContact extends Contact {
41 |     _patch(data) {
42 |         /**
43 |          * The contact's business profile
44 |          */
45 |         this.businessProfile = data.businessProfile;
46 | 
47 |         return super._patch(data);
48 |     }
49 | 
50 | }
51 | 
52 | module.exports = BusinessContact;
53 |
54 |
55 |
56 | 57 |
58 |
59 |
60 | 65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/Base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Class: Base 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 |
26 | 36 |
37 |
38 |
39 |

new Base()

40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/authStrategies_BaseAuthStrategy.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: authStrategies/BaseAuthStrategy.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
33 | 
34 | /**
35 |  * Base class which all authentication strategies extend
36 |  */
37 | class BaseAuthStrategy {
38 |     constructor() {}
39 |     setup(client) {
40 |         this.client = client;
41 |     }
42 |     async beforeBrowserInitialized() {}
43 |     async afterBrowserInitialized() {}
44 |     async onAuthenticationNeeded() {
45 |         return {
46 |             failed: false,
47 |             restart: false,
48 |             failureEventPayload: undefined
49 |         };
50 |     }
51 |     async getAuthEventPayload() {}
52 |     async logout() {}
53 | }
54 | 
55 | module.exports = BaseAuthStrategy;
56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/NoAuth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Class: NoAuth 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 |
26 | 37 |
38 |
39 |
40 |

new NoAuth()

41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/structures_ProductMetadata.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/ProductMetadata.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
const Base = require('./Base');
33 | 
34 | class ProductMetadata extends Base {
35 |     constructor(client, data) {
36 |         super(client);
37 | 
38 |         if (data) this._patch(data);
39 |     }
40 | 
41 |     _patch(data) {
42 |         /** Product ID */
43 |         this.id = data.id;
44 |         /** Retailer ID */
45 |         this.retailer_id = data.retailer_id;
46 |         /** Product Name  */
47 |         this.name = data.name;
48 |         /** Product Description */
49 |         this.description = data.description;
50 | 
51 |         return super._patch(data);
52 |     }
53 | 
54 | }
55 | 
56 | module.exports = ProductMetadata;
57 |
58 |
59 |
60 | 61 |
62 |
63 |
64 | 69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/BaseAuthStrategy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Class: BaseAuthStrategy 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 |
26 | 36 |
37 |
38 |
39 |

new BaseAuthStrategy()

40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/authStrategies/LegacySessionAuth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseAuthStrategy = require('./BaseAuthStrategy'); 4 | 5 | /** 6 | * Legacy session auth strategy 7 | * Not compatible with multi-device accounts. 8 | * @param {object} options - options 9 | * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails 10 | * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session 11 | * @param {string} options.session.WABrowserId 12 | * @param {string} options.session.WASecretBundle 13 | * @param {string} options.session.WAToken1 14 | * @param {string} options.session.WAToken2 15 | */ 16 | class LegacySessionAuth extends BaseAuthStrategy { 17 | constructor({ session, restartOnAuthFail }={}) { 18 | super(); 19 | this.session = session; 20 | this.restartOnAuthFail = restartOnAuthFail; 21 | } 22 | 23 | async afterBrowserInitialized() { 24 | if(this.session) { 25 | await this.client.pupPage.evaluateOnNewDocument(session => { 26 | if (document.referrer === 'https://whatsapp.com/') { 27 | localStorage.clear(); 28 | localStorage.setItem('WABrowserId', session.WABrowserId); 29 | localStorage.setItem('WASecretBundle', session.WASecretBundle); 30 | localStorage.setItem('WAToken1', session.WAToken1); 31 | localStorage.setItem('WAToken2', session.WAToken2); 32 | } 33 | 34 | localStorage.setItem('remember-me', 'true'); 35 | }, this.session); 36 | } 37 | } 38 | 39 | async onAuthenticationNeeded() { 40 | if(this.session) { 41 | this.session = null; 42 | return { 43 | failed: true, 44 | restart: this.restartOnAuthFail, 45 | failureEventPayload: 'Unable to log in. Are the session details valid?' 46 | }; 47 | } 48 | 49 | return { failed: false }; 50 | } 51 | 52 | async getAuthEventPayload() { 53 | const isMD = await this.client.pupPage.evaluate(() => { 54 | return window.Store.MDBackend; 55 | }); 56 | 57 | if(isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.'); 58 | 59 | const localStorage = JSON.parse(await this.client.pupPage.evaluate(() => { 60 | return JSON.stringify(window.localStorage); 61 | })); 62 | 63 | return { 64 | WABrowserId: localStorage.WABrowserId, 65 | WASecretBundle: localStorage.WASecretBundle, 66 | WAToken1: localStorage.WAToken1, 67 | WAToken2: localStorage.WAToken2 68 | }; 69 | } 70 | } 71 | 72 | module.exports = LegacySessionAuth; 73 | -------------------------------------------------------------------------------- /docs/structures_Location.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Location.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
33 | 
34 | /**
35 |  * Location information
36 |  */
37 | class Location {
38 |     /**
39 |      * @param {number} latitude
40 |      * @param {number} longitude
41 |      * @param {?string} description
42 |      */
43 |     constructor(latitude, longitude, description) {
44 |         /**
45 |          * Location latitude
46 |          * @type {number}
47 |          */
48 |         this.latitude = latitude;
49 | 
50 |         /**
51 |          * Location longitude
52 |          * @type {number}
53 |          */
54 |         this.longitude = longitude;
55 | 
56 |         /**
57 |          * Name for the location
58 |          * @type {?string}
59 |          */
60 |         this.description = description;
61 |     }
62 | }
63 | 
64 | module.exports = Location;
65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 | 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/structures/GroupNotification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Base = require('./Base'); 4 | 5 | /** 6 | * Represents a GroupNotification on WhatsApp 7 | * @extends {Base} 8 | */ 9 | class GroupNotification extends Base { 10 | constructor(client, data) { 11 | super(client); 12 | 13 | if(data) this._patch(data); 14 | } 15 | 16 | _patch(data) { 17 | /** 18 | * ID that represents the groupNotification 19 | * @type {object} 20 | */ 21 | this.id = data.id; 22 | 23 | /** 24 | * Extra content 25 | * @type {string} 26 | */ 27 | this.body = data.body || ''; 28 | 29 | /** 30 | * GroupNotification type 31 | * @type {GroupNotificationTypes} 32 | */ 33 | this.type = data.subtype; 34 | 35 | /** 36 | * Unix timestamp for when the groupNotification was created 37 | * @type {number} 38 | */ 39 | this.timestamp = data.t; 40 | 41 | /** 42 | * ID for the Chat that this groupNotification was sent for. 43 | * 44 | * @type {string} 45 | */ 46 | this.chatId = typeof (data.id.remote) === 'object' ? data.id.remote._serialized : data.id.remote; 47 | 48 | /** 49 | * ContactId for the user that produced the GroupNotification. 50 | * @type {string} 51 | */ 52 | this.author = typeof (data.author) === 'object' ? data.author._serialized : data.author; 53 | 54 | /** 55 | * Contact IDs for the users that were affected by this GroupNotification. 56 | * @type {Array} 57 | */ 58 | this.recipientIds = []; 59 | 60 | if (data.recipients) { 61 | this.recipientIds = data.recipients; 62 | } 63 | 64 | return super._patch(data); 65 | } 66 | 67 | /** 68 | * Returns the Chat this groupNotification was sent in 69 | * @returns {Promise} 70 | */ 71 | getChat() { 72 | return this.client.getChatById(this.chatId); 73 | } 74 | 75 | /** 76 | * Returns the Contact this GroupNotification was produced by 77 | * @returns {Promise} 78 | */ 79 | getContact() { 80 | return this.client.getContactById(this.author); 81 | } 82 | 83 | /** 84 | * Returns the Contacts affected by this GroupNotification. 85 | * @returns {Promise>} 86 | */ 87 | async getRecipients() { 88 | return await Promise.all(this.recipientIds.map(async m => await this.client.getContactById(m))); 89 | } 90 | 91 | /** 92 | * Sends a message to the same chat this GroupNotification was produced in. 93 | * 94 | * @param {string|MessageMedia|Location} content 95 | * @param {object} options 96 | * @returns {Promise} 97 | */ 98 | async reply(content, options={}) { 99 | return this.client.sendMessage(this.chatId, content, options); 100 | } 101 | 102 | } 103 | 104 | module.exports = GroupNotification; 105 | -------------------------------------------------------------------------------- /docs/structures_Label.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Label.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const Base = require('./Base');
 35 | // eslint-disable-next-line no-unused-vars
 36 | const Chat = require('./Chat');
 37 | 
 38 | /**
 39 |  * WhatsApp Business Label information
 40 |  */
 41 | class Label extends Base {
 42 |     /**
 43 |      * @param {Base} client
 44 |      * @param {object} labelData
 45 |      */
 46 |     constructor(client, labelData){
 47 |         super(client);
 48 | 
 49 |         if(labelData) this._patch(labelData);
 50 |     }
 51 | 
 52 |     _patch(labelData){
 53 |         /**
 54 |          * Label ID
 55 |          * @type {string}
 56 |          */
 57 |         this.id = labelData.id;
 58 | 
 59 |         /**
 60 |          * Label name
 61 |          * @type {string}
 62 |          */
 63 |         this.name = labelData.name;
 64 | 
 65 |         /**
 66 |          * Label hex color
 67 |          * @type {string}
 68 |          */
 69 |         this.hexColor = labelData.hexColor;
 70 |     }
 71 |     /**
 72 |      * Get all chats that have been assigned this Label
 73 |      * @returns {Promise<Array<Chat>>}
 74 |      */
 75 |     async getChats(){
 76 |         return this.client.getChatsByLabelId(this.id);
 77 |     }
 78 | 
 79 | }
 80 | 
 81 | module.exports = Label;
82 |
83 |
84 |
85 | 86 |
87 |
88 |
89 | 94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/structures_Order.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Order.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const Base = require('./Base');
 35 | const Product = require('./Product');
 36 | 
 37 | /**
 38 |  * Represents a Order on WhatsApp
 39 |  * @extends {Base}
 40 |  */
 41 | class Order extends Base {
 42 |     constructor(client, data) {
 43 |         super(client);
 44 | 
 45 |         if (data) this._patch(data);
 46 |     }
 47 | 
 48 |     _patch(data) {
 49 |         /**
 50 |          * List of products
 51 |          * @type {Array<Product>}
 52 |          */
 53 |         if (data.products) {
 54 |             this.products = data.products.map(product => new Product(this.client, product));
 55 |         }
 56 |         /**
 57 |          * Order Subtotal
 58 |          * @type {string}
 59 |          */
 60 |         this.subtotal = data.subtotal;
 61 |         /**
 62 |          * Order Total
 63 |          * @type {string}
 64 |          */
 65 |         this.total = data.total;
 66 |         /**
 67 |          * Order Currency
 68 |          * @type {string}
 69 |          */
 70 |         this.currency = data.currency;
 71 |         /**
 72 |          * Order Created At
 73 |          * @type {number}
 74 |          */
 75 |         this.createdAt = data.createdAt;
 76 | 
 77 |         return super._patch(data);
 78 |     }
 79 | 
 80 | 
 81 | }
 82 | 
 83 | module.exports = Order;
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/structures/MessageMedia.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const mime = require('mime'); 6 | const fetch = require('node-fetch'); 7 | const { URL } = require('url'); 8 | 9 | /** 10 | * Media attached to a message 11 | * @param {string} mimetype MIME type of the attachment 12 | * @param {string} data Base64-encoded data of the file 13 | * @param {?string} filename Document file name 14 | */ 15 | class MessageMedia { 16 | constructor(mimetype, data, filename) { 17 | /** 18 | * MIME type of the attachment 19 | * @type {string} 20 | */ 21 | this.mimetype = mimetype; 22 | 23 | /** 24 | * Base64 encoded data that represents the file 25 | * @type {string} 26 | */ 27 | this.data = data; 28 | 29 | /** 30 | * Name of the file (for documents) 31 | * @type {?string} 32 | */ 33 | this.filename = filename; 34 | } 35 | 36 | /** 37 | * Creates a MessageMedia instance from a local file path 38 | * @param {string} filePath 39 | * @returns {MessageMedia} 40 | */ 41 | static fromFilePath(filePath) { 42 | const b64data = fs.readFileSync(filePath, {encoding: 'base64'}); 43 | const mimetype = mime.getType(filePath); 44 | const filename = path.basename(filePath); 45 | 46 | return new MessageMedia(mimetype, b64data, filename); 47 | } 48 | 49 | /** 50 | * Creates a MessageMedia instance from a URL 51 | * @param {string} url 52 | * @param {Object} [options] 53 | * @param {boolean} [options.unsafeMime=false] 54 | * @param {string} [options.filename] 55 | * @param {object} [options.client] 56 | * @param {object} [options.reqOptions] 57 | * @param {number} [options.reqOptions.size=0] 58 | * @returns {Promise} 59 | */ 60 | static async fromUrl(url, options = {}) { 61 | const pUrl = new URL(url); 62 | let mimetype = mime.getType(pUrl.pathname); 63 | 64 | if (!mimetype && !options.unsafeMime) 65 | throw new Error('Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.'); 66 | 67 | async function fetchData (url, options) { 68 | const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options); 69 | const response = await fetch(url, reqOptions); 70 | const mime = response.headers.get('Content-Type'); 71 | 72 | const contentDisposition = response.headers.get('Content-Disposition'); 73 | const name = contentDisposition ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) : null; 74 | 75 | let data = ''; 76 | if (response.buffer) { 77 | data = (await response.buffer()).toString('base64'); 78 | } else { 79 | const bArray = new Uint8Array(await response.arrayBuffer()); 80 | bArray.forEach((b) => { 81 | data += String.fromCharCode(b); 82 | }); 83 | data = btoa(data); 84 | } 85 | 86 | return { data, mime, name }; 87 | } 88 | 89 | const res = options.client 90 | ? (await options.client.pupPage.evaluate(fetchData, url, options.reqOptions)) 91 | : (await fetchData(url, options.reqOptions)); 92 | 93 | const filename = options.filename || 94 | (res.name ? res.name[0] : (pUrl.pathname.split('/').pop() || 'file')); 95 | 96 | if (!mimetype) 97 | mimetype = res.mime; 98 | 99 | return new MessageMedia(mimetype, res.data, filename); 100 | } 101 | } 102 | 103 | module.exports = MessageMedia; 104 | -------------------------------------------------------------------------------- /src/util/InterfaceController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Interface Controller 5 | */ 6 | class InterfaceController { 7 | 8 | constructor(props) { 9 | this.pupPage = props.pupPage; 10 | } 11 | 12 | /** 13 | * Opens the Chat Window 14 | * @param {string} chatId ID of the chat window that will be opened 15 | */ 16 | async openChatWindow(chatId) { 17 | await this.pupPage.evaluate(async chatId => { 18 | let chatWid = window.Store.WidFactory.createWid(chatId); 19 | let chat = await window.Store.Chat.find(chatWid); 20 | await window.Store.Cmd.openChatAt(chat); 21 | }, chatId); 22 | } 23 | 24 | /** 25 | * Opens the Chat Drawer 26 | * @param {string} chatId ID of the chat drawer that will be opened 27 | */ 28 | async openChatDrawer(chatId) { 29 | await this.pupPage.evaluate(async chatId => { 30 | let chat = await window.Store.Chat.get(chatId); 31 | await window.Store.Cmd.openDrawerMid(chat); 32 | }, chatId); 33 | } 34 | 35 | /** 36 | * Opens the Chat Search 37 | * @param {string} chatId ID of the chat search that will be opened 38 | */ 39 | async openChatSearch(chatId) { 40 | await this.pupPage.evaluate(async chatId => { 41 | let chat = await window.Store.Chat.get(chatId); 42 | await window.Store.Cmd.chatSearch(chat); 43 | }, chatId); 44 | } 45 | 46 | /** 47 | * Opens or Scrolls the Chat Window to the position of the message 48 | * @param {string} msgId ID of the message that will be scrolled to 49 | */ 50 | async openChatWindowAt(msgId) { 51 | await this.pupPage.evaluate(async msgId => { 52 | let msg = await window.Store.Msg.get(msgId); 53 | await window.Store.Cmd.openChatAt(msg.chat, msg.chat.getSearchContext(msg)); 54 | }, msgId); 55 | } 56 | 57 | /** 58 | * Opens the Message Drawer 59 | * @param {string} msgId ID of the message drawer that will be opened 60 | */ 61 | async openMessageDrawer(msgId) { 62 | await this.pupPage.evaluate(async msgId => { 63 | let msg = await window.Store.Msg.get(msgId); 64 | await window.Store.Cmd.msgInfoDrawer(msg); 65 | }, msgId); 66 | } 67 | 68 | /** 69 | * Closes the Right Drawer 70 | */ 71 | async closeRightDrawer() { 72 | await this.pupPage.evaluate(async () => { 73 | await window.Store.Cmd.closeDrawerRight(); 74 | }); 75 | } 76 | 77 | /** 78 | * Get all Features 79 | */ 80 | async getFeatures() { 81 | return await this.pupPage.evaluate(() => { 82 | return window.Store.Features.F; 83 | }); 84 | } 85 | 86 | /** 87 | * Check if Feature is enabled 88 | * @param {string} feature status to check 89 | */ 90 | async checkFeatureStatus(feature) { 91 | return await this.pupPage.evaluate((feature) => { 92 | return window.Store.Features.supportsFeature(feature); 93 | }, feature); 94 | } 95 | 96 | /** 97 | * Enable Features 98 | * @param {string[]} features to be enabled 99 | */ 100 | async enableFeatures(features) { 101 | await this.pupPage.evaluate((features) => { 102 | for (const feature in features) { 103 | window.Store.Features.setFeature(features[feature], true); 104 | } 105 | }, features); 106 | } 107 | 108 | /** 109 | * Disable Features 110 | * @param {string[]} features to be disabled 111 | */ 112 | async disableFeatures(features) { 113 | await this.pupPage.evaluate((features) => { 114 | for (const feature in features) { 115 | window.Store.Features.setFeature(features[feature], false); 116 | } 117 | }, features); 118 | } 119 | } 120 | 121 | module.exports = InterfaceController; 122 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: '🐛 Bug report' 2 | description: Create a report to help us improve 3 | labels: bug 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for reporting an issue :pray:. 9 | 10 | This issue tracker is for reporting bugs found in [`whatsapp-web.js`](https://github.com/pedroslopez/whatsapp-web.js). 11 | 12 | If you have a question about how to achieve something and are struggling, please post a question in our [Discord server](https://discord.gg/wyKybbF) instead. 13 | 14 | Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: 15 | - `whatsapp-web.js` [Issues tab](https://github.com/pedroslopez/whatsapp-web.js/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) 16 | - `whatsapp-web.js` [closed Issues tab](https://github.com/pedroslopez/whatsapp-web.js/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed) 17 | 18 | The more information you fill in, the better the community can help you. 19 | - type: checkboxes 20 | attributes: 21 | label: Is there an existing issue for this? 22 | description: Please search to see if an issue already exists for the bug you encountered. 23 | options: 24 | - label: I have searched the existing issues 25 | required: true 26 | - type: textarea 27 | id: description 28 | attributes: 29 | label: Describe the bug 30 | description: Provide a clear and concise description of the challenge you are running into. 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: expected 35 | attributes: 36 | label: Expected behavior 37 | description: Provide a clear and concise description of what you expected to happen. 38 | placeholder: | 39 | As a user, I expected ___ behavior but I am seeing ___ 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: steps 44 | attributes: 45 | label: Steps to Reproduce the Bug or Issue 46 | description: Describe the steps we have to take to reproduce the behavior. 47 | placeholder: | 48 | 1. Do X 49 | 2. Do Y 50 | 3. Do Z 51 | 4. See error 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: relevant_code 56 | attributes: 57 | label: Relevant Code 58 | description: If applicable, add code snippets to help explain your problem. 59 | validations: 60 | required: false 61 | - type: dropdown 62 | id: browser_type 63 | attributes: 64 | label: Browser Type 65 | description: What web browser are you using? 66 | options: 67 | - Chromium 68 | - Google Chrome 69 | - Other (please write in Additional Context) 70 | validations: 71 | required: true 72 | - type: dropdown 73 | id: whatsapp_type 74 | attributes: 75 | label: WhatsApp Account Type 76 | options: 77 | - Standard 78 | - WhatsApp Business 79 | validations: 80 | required: true 81 | - type: dropdown 82 | id: multidevice 83 | attributes: 84 | label: Does your WhatsApp account have multidevice enabled? 85 | options: 86 | - Yes, I am using Multi Device 87 | - No, I am not using Multi Device 88 | validations: 89 | required: true 90 | - type: textarea 91 | attributes: 92 | label: Environment 93 | description: | 94 | - OS: [e.g. Mac, Windows, Linux, Docker + Ubuntu 18, etc] 95 | - Phone OS: [e.g. Android, iOS] 96 | - whatsapp-web.js version [e.g. 1.2.3] 97 | - WhatsApp Web version [run `await client.getWWebVersion()`]: 98 | - Node.js Version [e.g. 1.2.3] 99 | validations: 100 | required: true 101 | - type: textarea 102 | id: additional 103 | attributes: 104 | label: Additional context 105 | description: Add any other context about the problem here. 106 | -------------------------------------------------------------------------------- /docs/structures_Call.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Call.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const Base = require('./Base');
 35 | 
 36 | /**
 37 |  * Represents a Call on WhatsApp
 38 |  * @extends {Base}
 39 |  */
 40 | class Call extends Base {
 41 |     constructor(client, data) {
 42 |         super(client);
 43 | 
 44 |         if (data) this._patch(data);
 45 |     }
 46 | 
 47 |     _patch(data) {
 48 |         /**
 49 |          * Call ID
 50 |          * @type {string}
 51 |          */
 52 |         this.id = data.id;
 53 |         /**
 54 |          * From
 55 |          * @type {string}
 56 |          */
 57 |         this.from = data.peerJid;
 58 |         /**
 59 |          * Unix timestamp for when the call was created
 60 |          * @type {number}
 61 |          */
 62 |         this.timestamp = data.offerTime;
 63 |         /**
 64 |          * Is video
 65 |          * @type {boolean}
 66 |          */
 67 |         this.isVideo = data.isVideo;
 68 |         /**
 69 |          * Is Group
 70 |          * @type {boolean}
 71 |          */
 72 |         this.isGroup = data.isGroup;
 73 |         /**
 74 |          * Indicates if the call was sent by the current user
 75 |          * @type {boolean}
 76 |          */
 77 |         this.fromMe = data.outgoing;
 78 |         /**
 79 |          * Indicates if the call can be handled in waweb
 80 |          * @type {boolean}
 81 |          */
 82 |         this.canHandleLocally = data.canHandleLocally;
 83 |         /**
 84 |          * Indicates if the call Should be handled in waweb
 85 |          * @type {boolean}
 86 |          */
 87 |         this.webClientShouldHandle = data.webClientShouldHandle;
 88 |         /**
 89 |          * Object with participants
 90 |          * @type {object}
 91 |          */
 92 |         this.participants = data.participants;
 93 |         
 94 |         return super._patch(data);
 95 |     }
 96 |     
 97 | }
 98 | 
 99 | module.exports = Call;
100 |
101 |
102 |
103 | 104 |
105 |
106 |
107 | 112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/structures_Product.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Product.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const Base = require('./Base');
 35 | const ProductMetadata = require('./ProductMetadata');
 36 | 
 37 | /**
 38 |  * Represents a Product on WhatsAppBusiness
 39 |  * @extends {Base}
 40 |  */
 41 | class Product extends Base {
 42 |     constructor(client, data) {
 43 |         super(client);
 44 | 
 45 |         if (data) this._patch(data);
 46 |     }
 47 | 
 48 |     _patch(data) {
 49 |         /**
 50 |          * Product ID
 51 |          * @type {string}
 52 |          */
 53 |         this.id = data.id;
 54 |         /**
 55 |          * Price
 56 |          * @type {string}
 57 |          */
 58 |         this.price = data.price ? data.price : '';
 59 |         /**
 60 |          * Product Thumbnail
 61 |          * @type {string}
 62 |          */
 63 |         this.thumbnailUrl = data.thumbnailUrl;
 64 |         /**
 65 |          * Currency
 66 |          * @type {string}
 67 |          */
 68 |         this.currency = data.currency;
 69 |         /**
 70 |          * Product Name
 71 |          * @type {string}
 72 |          */
 73 |         this.name = data.name;
 74 |         /**
 75 |          * Product Quantity
 76 |          * @type {number}
 77 |          */
 78 |         this.quantity = data.quantity;
 79 |         /** Product metadata */
 80 |         this.data = null;
 81 |         return super._patch(data);
 82 |     }
 83 | 
 84 |     async getData() {
 85 |         if (this.data === null) {
 86 |             let result = await this.client.pupPage.evaluate((productId) => {
 87 |                 return window.WWebJS.getProductMetadata(productId);
 88 |             }, this.id);
 89 |             if (!result) {
 90 |                 this.data = undefined;
 91 |             } else {
 92 |                 this.data = new ProductMetadata(this.client, result);
 93 |             }
 94 |         }
 95 |         return this.data;
 96 |     }
 97 | }
 98 | 
 99 | module.exports = Product;
100 |
101 |
102 |
103 | 104 |
105 |
106 |
107 | 112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /tests/structures/message.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const sinon = require('sinon'); 3 | 4 | const helper = require('../helper'); 5 | const { Contact, Chat } = require('../../src/structures'); 6 | 7 | const remoteId = helper.remoteId; 8 | 9 | describe('Message', function () { 10 | let client; 11 | let chat; 12 | let message; 13 | 14 | before(async function() { 15 | this.timeout(35000); 16 | client = helper.createClient({ authenticated: true }); 17 | await client.initialize(); 18 | 19 | chat = await client.getChatById(remoteId); 20 | message = await chat.sendMessage('this is only a test'); 21 | 22 | // wait for message to be sent 23 | await helper.sleep(1000); 24 | }); 25 | 26 | after(async function () { 27 | await client.destroy(); 28 | }); 29 | 30 | it('can get the related chat', async function () { 31 | const chat = await message.getChat(); 32 | expect(chat).to.be.instanceOf(Chat); 33 | expect(chat.id._serialized).to.equal(remoteId); 34 | }); 35 | 36 | it('can get the related contact', async function () { 37 | const contact = await message.getContact(); 38 | expect(contact).to.be.instanceOf(Contact); 39 | expect(contact.id._serialized).to.equal(client.info.wid._serialized); 40 | }); 41 | 42 | it('can get message info', async function () { 43 | const info = await message.getInfo(); 44 | expect(typeof info).to.equal('object'); 45 | expect(Array.isArray(info.played)).to.equal(true); 46 | expect(Array.isArray(info.read)).to.equal(true); 47 | expect(Array.isArray(info.delivery)).to.equal(true); 48 | }); 49 | 50 | describe('Replies', function () { 51 | let replyMsg; 52 | 53 | it('can reply to a message', async function () { 54 | replyMsg = await message.reply('this is my reply'); 55 | expect(replyMsg.hasQuotedMsg).to.equal(true); 56 | }); 57 | 58 | it('can get the quoted message', async function () { 59 | const quotedMsg = await replyMsg.getQuotedMessage(); 60 | expect(quotedMsg.id._serialized).to.equal(message.id._serialized); 61 | }); 62 | }); 63 | 64 | describe('Star', function () { 65 | it('can star a message', async function () { 66 | expect(message.isStarred).to.equal(false); 67 | await message.star(); 68 | 69 | // reload and check 70 | await message.reload(); 71 | expect(message.isStarred).to.equal(true); 72 | }); 73 | 74 | it('can un-star a message', async function () { 75 | expect(message.isStarred).to.equal(true); 76 | await message.unstar(); 77 | 78 | // reload and check 79 | await message.reload(); 80 | expect(message.isStarred).to.equal(false); 81 | }); 82 | }); 83 | 84 | describe('Delete', function () { 85 | it('can delete a message for me', async function () { 86 | await message.delete(); 87 | 88 | await helper.sleep(1000); 89 | expect(await message.reload()).to.equal(null); 90 | }); 91 | 92 | it('can delete a message for everyone', async function () { 93 | message = await chat.sendMessage('sneaky message'); 94 | await helper.sleep(1000); 95 | 96 | const callback = sinon.spy(); 97 | client.once('message_revoke_everyone', callback); 98 | 99 | await message.delete(true); 100 | await helper.sleep(1000); 101 | 102 | expect(await message.reload()).to.equal(null); 103 | expect(callback.called).to.equal(true); 104 | const [ revokeMsg, originalMsg ] = callback.args[0]; 105 | expect(revokeMsg.id._serialized).to.equal(originalMsg.id._serialized); 106 | expect(originalMsg.body).to.equal('sneaky message'); 107 | expect(originalMsg.type).to.equal('chat'); 108 | expect(revokeMsg.body).to.equal(''); 109 | expect(revokeMsg.type).to.equal('revoked'); 110 | }); 111 | }); 112 | }); -------------------------------------------------------------------------------- /src/util/Constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.WhatsWebURL = 'https://web.whatsapp.com/'; 4 | 5 | exports.DefaultOptions = { 6 | puppeteer: { 7 | headless: true, 8 | defaultViewport: null 9 | }, 10 | authTimeoutMs: 0, 11 | qrMaxRetries: 0, 12 | takeoverOnConflict: false, 13 | takeoverTimeoutMs: 0, 14 | userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36', 15 | ffmpegPath: 'ffmpeg', 16 | bypassCSP: false 17 | }; 18 | 19 | /** 20 | * Client status 21 | * @readonly 22 | * @enum {number} 23 | */ 24 | exports.Status = { 25 | INITIALIZING: 0, 26 | AUTHENTICATING: 1, 27 | READY: 3 28 | }; 29 | 30 | /** 31 | * Events that can be emitted by the client 32 | * @readonly 33 | * @enum {string} 34 | */ 35 | exports.Events = { 36 | AUTHENTICATED: 'authenticated', 37 | AUTHENTICATION_FAILURE: 'auth_failure', 38 | READY: 'ready', 39 | MESSAGE_RECEIVED: 'message', 40 | MESSAGE_CREATE: 'message_create', 41 | MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone', 42 | MESSAGE_REVOKED_ME: 'message_revoke_me', 43 | MESSAGE_ACK: 'message_ack', 44 | MEDIA_UPLOADED: 'media_uploaded', 45 | GROUP_JOIN: 'group_join', 46 | GROUP_LEAVE: 'group_leave', 47 | GROUP_UPDATE: 'group_update', 48 | QR_RECEIVED: 'qr', 49 | DISCONNECTED: 'disconnected', 50 | STATE_CHANGED: 'change_state', 51 | BATTERY_CHANGED: 'change_battery', 52 | INCOMING_CALL: 'incoming_call' 53 | }; 54 | 55 | /** 56 | * Message types 57 | * @readonly 58 | * @enum {string} 59 | */ 60 | exports.MessageTypes = { 61 | TEXT: 'chat', 62 | AUDIO: 'audio', 63 | VOICE: 'ptt', 64 | IMAGE: 'image', 65 | VIDEO: 'video', 66 | DOCUMENT: 'document', 67 | STICKER: 'sticker', 68 | LOCATION: 'location', 69 | CONTACT_CARD: 'vcard', 70 | CONTACT_CARD_MULTI: 'multi_vcard', 71 | ORDER: 'order', 72 | REVOKED: 'revoked', 73 | PRODUCT: 'product', 74 | UNKNOWN: 'unknown', 75 | GROUP_INVITE: 'groups_v4_invite', 76 | LIST: 'list', 77 | LIST_RESPONSE: 'list_response', 78 | BUTTONS_RESPONSE: 'buttons_response', 79 | PAYMENT: 'payment', 80 | BROADCAST_NOTIFICATION: 'broadcast_notification', 81 | CALL_LOG: 'call_log', 82 | CIPHERTEXT: 'ciphertext', 83 | DEBUG: 'debug', 84 | E2E_NOTIFICATION: 'e2e_notification', 85 | GP2: 'gp2', 86 | GROUP_NOTIFICATION: 'group_notification', 87 | HSM: 'hsm', 88 | INTERACTIVE: 'interactive', 89 | NATIVE_FLOW: 'native_flow', 90 | NOTIFICATION: 'notification', 91 | NOTIFICATION_TEMPLATE: 'notification_template', 92 | OVERSIZED: 'oversized', 93 | PROTOCOL: 'protocol', 94 | REACTION: 'reaction', 95 | TEMPLATE_BUTTON_REPLY: 'template_button_reply', 96 | }; 97 | 98 | /** 99 | * Group notification types 100 | * @readonly 101 | * @enum {string} 102 | */ 103 | exports.GroupNotificationTypes = { 104 | ADD: 'add', 105 | INVITE: 'invite', 106 | REMOVE: 'remove', 107 | LEAVE: 'leave', 108 | SUBJECT: 'subject', 109 | DESCRIPTION: 'description', 110 | PICTURE: 'picture', 111 | ANNOUNCE: 'announce', 112 | RESTRICT: 'restrict', 113 | }; 114 | 115 | /** 116 | * Chat types 117 | * @readonly 118 | * @enum {string} 119 | */ 120 | exports.ChatTypes = { 121 | SOLO: 'solo', 122 | GROUP: 'group', 123 | UNKNOWN: 'unknown' 124 | }; 125 | 126 | /** 127 | * WhatsApp state 128 | * @readonly 129 | * @enum {string} 130 | */ 131 | exports.WAState = { 132 | CONFLICT: 'CONFLICT', 133 | CONNECTED: 'CONNECTED', 134 | DEPRECATED_VERSION: 'DEPRECATED_VERSION', 135 | OPENING: 'OPENING', 136 | PAIRING: 'PAIRING', 137 | PROXYBLOCK: 'PROXYBLOCK', 138 | SMB_TOS_BLOCK: 'SMB_TOS_BLOCK', 139 | TIMEOUT: 'TIMEOUT', 140 | TOS_BLOCK: 'TOS_BLOCK', 141 | UNLAUNCHED: 'UNLAUNCHED', 142 | UNPAIRED: 'UNPAIRED', 143 | UNPAIRED_IDLE: 'UNPAIRED_IDLE' 144 | }; 145 | 146 | /** 147 | * Message ACK 148 | * @readonly 149 | * @enum {number} 150 | */ 151 | exports.MessageAck = { 152 | ACK_ERROR: -1, 153 | ACK_PENDING: 0, 154 | ACK_SERVER: 1, 155 | ACK_DEVICE: 2, 156 | ACK_READ: 3, 157 | ACK_PLAYED: 4, 158 | }; 159 | -------------------------------------------------------------------------------- /docs/authStrategies_LocalAuth.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: authStrategies/LocalAuth.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const path = require('path');
 35 | const fs = require('fs');
 36 | const BaseAuthStrategy = require('./BaseAuthStrategy');
 37 | 
 38 | /**
 39 |  * Local directory-based authentication
 40 |  * @param {object} options - options
 41 |  * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
 42 |  * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" 
 43 | */
 44 | class LocalAuth extends BaseAuthStrategy {
 45 |     constructor({ clientId, dataPath }={}) {
 46 |         super();
 47 | 
 48 |         const idRegex = /^[-_\w]+$/i;
 49 |         if(clientId &amp;&amp; !idRegex.test(clientId)) {
 50 |             throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.');
 51 |         }
 52 | 
 53 |         this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
 54 |         this.clientId = clientId;
 55 |     }
 56 | 
 57 |     async beforeBrowserInitialized() {
 58 |         const puppeteerOpts = this.client.options.puppeteer;
 59 |         const sessionDirName = this.clientId ? `session-${this.clientId}` : 'session';
 60 |         const dirPath = path.join(this.dataPath, sessionDirName);
 61 | 
 62 |         if(puppeteerOpts.userDataDir &amp;&amp; puppeteerOpts.userDataDir !== dirPath) {
 63 |             throw new Error('LocalAuth is not compatible with a user-supplied userDataDir.');
 64 |         }
 65 | 
 66 |         fs.mkdirSync(dirPath, { recursive: true });
 67 |         
 68 |         this.client.options.puppeteer = {
 69 |             ...puppeteerOpts,
 70 |             userDataDir: dirPath
 71 |         };
 72 | 
 73 |         this.userDataDir = dirPath;
 74 |     }
 75 | 
 76 |     async logout() {
 77 |         if (this.userDataDir) {
 78 |             return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true });
 79 |         }
 80 |     }
 81 | 
 82 | }
 83 | 
 84 | module.exports = LocalAuth;
85 |
86 |
87 |
88 | 89 |
90 |
91 |
92 | 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/structures_Payment.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Payment.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
const Base = require('./Base');
 33 | 
 34 | class Payment extends Base {
 35 |     constructor(client, data) {
 36 |         super(client);
 37 | 
 38 |         if (data) this._patch(data);
 39 |     }
 40 | 
 41 |     _patch(data) {
 42 |         /**
 43 |          * The payment Id
 44 |          * @type {object}
 45 |          */
 46 |         this.id = data.id;
 47 | 
 48 |         /**
 49 |          * The payment currency
 50 |          * @type {string}
 51 |          */
 52 |         this.paymentCurrency = data.paymentCurrency;
 53 | 
 54 |         /**
 55 |          * The payment ammount ( R$ 1.00 = 1000 )
 56 |          * @type {number}
 57 |          */
 58 |         this.paymentAmount1000 = data.paymentAmount1000;
 59 | 
 60 |         /**
 61 |          * The payment receiver
 62 |          * @type {object}
 63 |          */
 64 |         this.paymentMessageReceiverJid = data.paymentMessageReceiverJid;
 65 | 
 66 |         /**
 67 |          * The payment transaction timestamp
 68 |          * @type {number}
 69 |          */
 70 |         this.paymentTransactionTimestamp = data.paymentTransactionTimestamp;
 71 | 
 72 |         /**
 73 |          * The paymentStatus
 74 |          *
 75 |          * Possible Status
 76 |          * 0:UNKNOWN_STATUS
 77 |          * 1:PROCESSING
 78 |          * 2:SENT
 79 |          * 3:NEED_TO_ACCEPT
 80 |          * 4:COMPLETE
 81 |          * 5:COULD_NOT_COMPLETE
 82 |          * 6:REFUNDED
 83 |          * 7:EXPIRED
 84 |          * 8:REJECTED
 85 |          * 9:CANCELLED
 86 |          * 10:WAITING_FOR_PAYER
 87 |          * 11:WAITING
 88 |          * 
 89 |          * @type {number}
 90 |          */
 91 |         this.paymentStatus = data.paymentStatus;
 92 | 
 93 |         /**
 94 |          * Integer that represents the payment Text
 95 |          * @type {number}
 96 |          */
 97 |         this.paymentTxnStatus = data.paymentTxnStatus;
 98 | 
 99 |         /**
100 |          * The note sent with the payment
101 |          * @type {string}
102 |          */
103 |         this.paymentNote = !data.paymentNoteMsg ? undefined : data.paymentNoteMsg.body ?  data.paymentNoteMsg.body : undefined ;
104 | 
105 |         return super._patch(data);
106 |     }
107 | 
108 | }
109 | 
110 | module.exports = Payment;
111 | 
112 |
113 |
114 |
115 | 116 |
117 |
118 |
119 | 124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /docs/structures_ClientInfo.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/ClientInfo.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const Base = require('./Base');
 35 | 
 36 | /**
 37 |  * Current connection information
 38 |  * @extends {Base}
 39 |  */
 40 | class ClientInfo extends Base {
 41 |     constructor(client, data) {
 42 |         super(client);
 43 | 
 44 |         if (data) this._patch(data);
 45 |     }
 46 | 
 47 |     _patch(data) {
 48 |         /**
 49 |          * Name configured to be shown in push notifications
 50 |          * @type {string}
 51 |          */
 52 |         this.pushname = data.pushname;
 53 | 
 54 |         /**
 55 |          * Current user ID
 56 |          * @type {object}
 57 |          */
 58 |         this.wid = data.wid;
 59 | 
 60 |         /**
 61 |          * @type {object}
 62 |          * @deprecated Use .wid instead
 63 |          */
 64 |         this.me = data.wid;
 65 | 
 66 |         /**
 67 |          * Information about the phone this client is connected to. Not available in multi-device.
 68 |          * @type {object}
 69 |          * @property {string} wa_version WhatsApp Version running on the phone
 70 |          * @property {string} os_version OS Version running on the phone (iOS or Android version)
 71 |          * @property {string} device_manufacturer Device manufacturer
 72 |          * @property {string} device_model Device model
 73 |          * @property {string} os_build_number OS build number
 74 |          * @deprecated
 75 |          */
 76 |         this.phone = data.phone;
 77 | 
 78 |         /**
 79 |          * Platform WhatsApp is running on
 80 |          * @type {string}
 81 |          */
 82 |         this.platform = data.platform;
 83 | 
 84 |         return super._patch(data);
 85 |     }
 86 | 
 87 |     /**
 88 |      * Get current battery percentage and charging status for the attached device
 89 |      * @returns {object} batteryStatus
 90 |      * @returns {number} batteryStatus.battery - The current battery percentage
 91 |      * @returns {boolean} batteryStatus.plugged - Indicates if the phone is plugged in (true) or not (false)
 92 |      * @deprecated
 93 |      */
 94 |     async getBatteryStatus() {
 95 |         return await this.client.pupPage.evaluate(() => {
 96 |             const { battery, plugged } = window.Store.Conn;
 97 |             return { battery, plugged };
 98 |         });
 99 |     }
100 | }
101 | 
102 | module.exports = ClientInfo;
103 |
104 |
105 |
106 | 107 |
108 |
109 |
110 | 115 |
116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/whatsapp-web.js.svg)](https://www.npmjs.com/package/whatsapp-web.js) [![Depfu](https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg)](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ![WhatsApp_Web 2.2212.8](https://img.shields.io/badge/WhatsApp_Web-2.2212.8-brightgreen.svg) [![Discord Chat](https://img.shields.io/discord/698610475432411196.svg?logo=discord)](https://discord.gg/H7DqQs4) 2 | 3 | # whatsapp-web.js 4 | A WhatsApp API client that connects through the WhatsApp Web browser app 5 | 6 | It uses Puppeteer to run a real instance of Whatsapp Web to avoid getting blocked. 7 | 8 | **NOTE:** I can't guarantee you will not be blocked by using this method, although it has worked for me. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe. 9 | 10 | ## Quick Links 11 | 12 | * [Guide / Getting Started](https://wwebjs.dev/guide) _(work in progress)_ 13 | * [Reference documentation](https://docs.wwebjs.dev/) 14 | * [GitHub](https://github.com/pedroslopez/whatsapp-web.js) 15 | * [npm](https://npmjs.org/package/whatsapp-web.js) 16 | 17 | ## Installation 18 | 19 | The module is now available on npm! `npm i whatsapp-web.js` 20 | 21 | Please note that Node v12+ is required. 22 | 23 | ## Example usage 24 | 25 | ```js 26 | const { Client } = require('whatsapp-web.js'); 27 | 28 | const client = new Client(); 29 | 30 | client.on('qr', (qr) => { 31 | // Generate and scan this code with your phone 32 | console.log('QR RECEIVED', qr); 33 | }); 34 | 35 | client.on('ready', () => { 36 | console.log('Client is ready!'); 37 | }); 38 | 39 | client.on('message', msg => { 40 | if (msg.body == '!ping') { 41 | msg.reply('pong'); 42 | } 43 | }); 44 | 45 | client.initialize(); 46 | ``` 47 | 48 | Take a look at [example.js](https://github.com/pedroslopez/whatsapp-web.js/blob/master/example.js) for another example with more use cases. 49 | 50 | For more information on saving and restoring sessions, check out the available [Authentication Strategies](https://wwebjs.dev/guide/authentication.html). 51 | 52 | 53 | ## Supported features 54 | 55 | | Feature | Status | 56 | | ------------- | ------------- | 57 | | Multi Device | ✅ | 58 | | Send messages | ✅ | 59 | | Receive messages | ✅ | 60 | | Send media (images/audio/documents) | ✅ | 61 | | Send media (video) | ✅ [(requires google chrome)](https://wwebjs.dev/guide/handling-attachments.html#caveat-for-sending-videos-and-gifs) | 62 | | Send stickers | ✅ | 63 | | Receive media (images/audio/video/documents) | ✅ | 64 | | Send contact cards | ✅ | 65 | | Send location | ✅ | 66 | | Send buttons | ✅ | 67 | | Send lists | ✅ (business accounts not supported) | 68 | | Receive location | ✅ | 69 | | Message replies | ✅ | 70 | | Join groups by invite | ✅ | 71 | | Get invite for group | ✅ | 72 | | Modify group info (subject, description) | ✅ | 73 | | Modify group settings (send messages, edit info) | ✅ | 74 | | Add group participants | ✅ | 75 | | Kick group participants | ✅ | 76 | | Promote/demote group participants | ✅ | 77 | | Mention users | ✅ | 78 | | Mute/unmute chats | ✅ | 79 | | Block/unblock contacts | ✅ | 80 | | Get contact info | ✅ | 81 | | Get profile pictures | ✅ | 82 | | Set user status message | ✅ | 83 | 84 | Something missing? Make an issue and let us know! 85 | 86 | ## Contributing 87 | 88 | Pull requests are welcome! If you see something you'd like to add, please do. For drastic changes, please open an issue first. 89 | 90 | ## Supporting the project 91 | 92 | You can support the maintainer of this project through the links below 93 | 94 | - [Support via GitHub Sponsors](https://github.com/sponsors/pedroslopez) 95 | - [Support via PayPal](https://www.paypal.me/psla/) 96 | - [Sign up for DigitalOcean](https://m.do.co/c/73f906a36ed4) and get $100 in credit when you sign up (Referral) 97 | 98 | ## Disclaimer 99 | 100 | This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or its affiliates. The official WhatsApp website can be found at https://whatsapp.com. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners. 101 | 102 | ## License 103 | 104 | Copyright 2019 Pedro S Lopez 105 | 106 | Licensed under the Apache License, Version 2.0 (the "License"); 107 | you may not use this project except in compliance with the License. 108 | You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 109 | 110 | Unless required by applicable law or agreed to in writing, software 111 | distributed under the License is distributed on an "AS IS" BASIS, 112 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 113 | See the License for the specific language governing permissions and 114 | limitations under the License. 115 | -------------------------------------------------------------------------------- /docs/Order.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Class: Order 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 |
26 | 36 |
37 |
38 |

Properties

39 |
40 |
41 |
42 |
createdAt
43 |
44 |
45 |
currency
46 |
47 |
48 |
49 |
50 |
51 |
52 |
subtotal
53 |
54 |
55 |
total
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |

new Order()

67 |
68 |
Extends
69 |
Base
70 |
71 |
72 |
73 |

Properties

74 |
75 |

createdAt 76 |  number

77 |

Order Created At

78 |
79 |
80 |

currency 81 |  string

82 |

Order Currency

83 |
84 |
85 |

subtotal 86 |  string

87 |

Order Subtotal

88 |
89 |
90 |

total 91 |  string

92 |

Order Total

93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
103 | 108 |
109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/structures_Buttons.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/Buttons.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const MessageMedia = require('./MessageMedia');
 35 | const Util = require('../util/Util');
 36 | 
 37 | /**
 38 |  * Button spec used in Buttons constructor
 39 |  * @typedef {Object} ButtonSpec
 40 |  * @property {string=} id - Custom ID to set on the button. A random one will be generated if one is not passed.
 41 |  * @property {string} body - The text to show on the button.
 42 |  */
 43 | 
 44 | /**
 45 |  * @typedef {Object} FormattedButtonSpec
 46 |  * @property {string} buttonId
 47 |  * @property {number} type
 48 |  * @property {Object} buttonText
 49 |  */
 50 | 
 51 | /**
 52 |  * Message type buttons
 53 |  */
 54 | class Buttons {
 55 |     /**
 56 |      * @param {string|MessageMedia} body
 57 |      * @param {ButtonSpec[]} buttons - See {@link ButtonSpec}
 58 |      * @param {string?} title
 59 |      * @param {string?} footer
 60 |      */
 61 |     constructor(body, buttons, title, footer) {
 62 |         /**
 63 |          * Message body
 64 |          * @type {string|MessageMedia}
 65 |          */
 66 |         this.body = body;
 67 | 
 68 |         /**
 69 |          * title of message
 70 |          * @type {string}
 71 |          */
 72 |         this.title = title;
 73 |         
 74 |         /**
 75 |          * footer of message
 76 |          * @type {string}
 77 |          */
 78 |         this.footer = footer;
 79 | 
 80 |         if (body instanceof MessageMedia) {
 81 |             this.type = 'media';
 82 |             this.title = '';
 83 |         }else{
 84 |             this.type = 'chat';
 85 |         }
 86 | 
 87 |         /**
 88 |          * buttons of message
 89 |          * @type {FormattedButtonSpec[]}
 90 |          */
 91 |         this.buttons = this._format(buttons);
 92 |         if(!this.buttons.length){ throw '[BT01] No buttons';}
 93 |                 
 94 |     }
 95 | 
 96 |     /**
 97 |      * Creates button array from simple array
 98 |      * @param {ButtonSpec[]} buttons
 99 |      * @returns {FormattedButtonSpec[]}
100 |      * @example 
101 |      * Input: [{id:'customId',body:'button1'},{body:'button2'},{body:'button3'},{body:'button4'}]
102 |      * Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}]
103 |      */
104 |     _format(buttons){
105 |         buttons = buttons.slice(0,3); // phone users can only see 3 buttons, so lets limit this
106 |         return buttons.map((btn) => {
107 |             return {'buttonId':btn.id ? String(btn.id) : Util.generateHash(6),'buttonText':{'displayText':btn.body},'type':1};
108 |         });
109 |     }
110 |     
111 | }
112 | 
113 | module.exports = Buttons;
114 |
115 |
116 |
117 | 118 |
119 |
120 |
121 | 126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /docs/structures_List.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/List.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const Util = require('../util/Util');
 35 | 
 36 | /**
 37 |  * Message type List
 38 |  */
 39 | class List {
 40 |     /**
 41 |      * @param {string} body
 42 |      * @param {string} buttonText
 43 |      * @param {Array&lt;any>} sections
 44 |      * @param {string?} title
 45 |      * @param {string?} footer
 46 |      */
 47 |     constructor(body, buttonText, sections, title, footer) {
 48 |         /**
 49 |          * Message body
 50 |          * @type {string}
 51 |          */
 52 |         this.description = body;
 53 | 
 54 |         /**
 55 |          * List button text
 56 |          * @type {string}
 57 |          */
 58 |         this.buttonText = buttonText;
 59 |         
 60 |         /**
 61 |          * title of message
 62 |          * @type {string}
 63 |          */
 64 |         this.title = title;
 65 |         
 66 | 
 67 |         /**
 68 |          * footer of message
 69 |          * @type {string}
 70 |          */
 71 |         this.footer = footer;
 72 | 
 73 |         /**
 74 |          * sections of message
 75 |          * @type {Array&lt;any>}
 76 |          */
 77 |         this.sections = this._format(sections);
 78 |         
 79 |     }
 80 |     
 81 |     /**
 82 |      * Creates section array from simple array
 83 |      * @param {Array&lt;any>} sections
 84 |      * @returns {Array&lt;any>}
 85 |      * @example
 86 |      * Input: [{title:'sectionTitle',rows:[{id:'customId', title:'ListItem2', description: 'desc'},{title:'ListItem2'}]}}]
 87 |      * Returns: [{'title':'sectionTitle','rows':[{'rowId':'customId','title':'ListItem1','description':'desc'},{'rowId':'oGSRoD','title':'ListItem2','description':''}]}]
 88 |      */
 89 |     _format(sections){
 90 |         if(!sections.length){throw '[LT02] List without sections';}
 91 |         if(sections.length > 1 &amp;&amp; sections.filter(s => typeof s.title == 'undefined').length > 1){throw '[LT05] You can\'t have more than one empty title.';}
 92 |         return sections.map( (section) =>{
 93 |             if(!section.rows.length){throw '[LT03] Section without rows';}
 94 |             return {
 95 |                 title: section.title ? section.title : undefined,
 96 |                 rows: section.rows.map( (row) => {
 97 |                     if(!row.title){throw '[LT04] Row without title';}
 98 |                     return {
 99 |                         rowId: row.id ? row.id : Util.generateHash(6),
100 |                         title: row.title,
101 |                         description: row.description ? row.description : ''
102 |                     };
103 |                 })
104 |             };
105 |         });
106 |     }
107 |     
108 | }
109 | 
110 | module.exports = List;
111 | 
112 |
113 |
114 |
115 | 116 |
117 |
118 |
119 | 124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /docs/authStrategies_LegacySessionAuth.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: authStrategies/LegacySessionAuth.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const BaseAuthStrategy = require('./BaseAuthStrategy');
 35 | 
 36 | /**
 37 |  * Legacy session auth strategy
 38 |  * Not compatible with multi-device accounts.
 39 |  * @param {object} options - options
 40 |  * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails
 41 |  * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session
 42 |  * @param {string} options.session.WABrowserId
 43 |  * @param {string} options.session.WASecretBundle
 44 |  * @param {string} options.session.WAToken1
 45 |  * @param {string} options.session.WAToken2
 46 |  */
 47 | class LegacySessionAuth extends BaseAuthStrategy {
 48 |     constructor({ session, restartOnAuthFail }={}) {
 49 |         super();
 50 |         this.session = session;
 51 |         this.restartOnAuthFail = restartOnAuthFail;
 52 |     }
 53 | 
 54 |     async afterBrowserInitialized() {
 55 |         if(this.session) {
 56 |             await this.client.pupPage.evaluateOnNewDocument(session => {
 57 |                 if (document.referrer === 'https://whatsapp.com/') {
 58 |                     localStorage.clear();
 59 |                     localStorage.setItem('WABrowserId', session.WABrowserId);
 60 |                     localStorage.setItem('WASecretBundle', session.WASecretBundle);
 61 |                     localStorage.setItem('WAToken1', session.WAToken1);
 62 |                     localStorage.setItem('WAToken2', session.WAToken2);
 63 |                 }
 64 |   
 65 |                 localStorage.setItem('remember-me', 'true');
 66 |             }, this.session);
 67 |         }
 68 |     }
 69 | 
 70 |     async onAuthenticationNeeded() {
 71 |         if(this.session) {
 72 |             this.session = null;
 73 |             return {
 74 |                 failed: true,
 75 |                 restart: this.restartOnAuthFail,
 76 |                 failureEventPayload: 'Unable to log in. Are the session details valid?'
 77 |             };
 78 |         }
 79 | 
 80 |         return { failed: false };
 81 |     }
 82 | 
 83 |     async getAuthEventPayload() {
 84 |         const isMD = await this.client.pupPage.evaluate(() => {
 85 |             return window.Store.MDBackend;
 86 |         });
 87 | 
 88 |         if(isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.');
 89 | 
 90 |         const localStorage = JSON.parse(await this.client.pupPage.evaluate(() => {
 91 |             return JSON.stringify(window.localStorage);
 92 |         }));
 93 | 
 94 |         return {
 95 |             WABrowserId: localStorage.WABrowserId,
 96 |             WASecretBundle: localStorage.WASecretBundle,
 97 |             WAToken1: localStorage.WAToken1,
 98 |             WAToken2: localStorage.WAToken2
 99 |         };
100 |     }
101 | }
102 | 
103 | module.exports = LegacySessionAuth;
104 | 
105 |
106 |
107 |
108 | 109 |
110 |
111 |
112 | 117 |
118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /docs/structures_GroupNotification.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/GroupNotification.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const Base = require('./Base');
 35 | 
 36 | /**
 37 |  * Represents a GroupNotification on WhatsApp
 38 |  * @extends {Base}
 39 |  */
 40 | class GroupNotification extends Base {
 41 |     constructor(client, data) {
 42 |         super(client);
 43 | 
 44 |         if(data) this._patch(data);
 45 |     }
 46 | 
 47 |     _patch(data) {
 48 |         /**
 49 |          * ID that represents the groupNotification
 50 |          * @type {object}
 51 |          */
 52 |         this.id = data.id;
 53 | 
 54 |         /**
 55 |          * Extra content
 56 |          * @type {string}
 57 |          */
 58 |         this.body = data.body || '';
 59 | 
 60 |         /** 
 61 |          * GroupNotification type
 62 |          * @type {GroupNotificationTypes}
 63 |          */
 64 |         this.type = data.subtype;
 65 |         
 66 |         /**
 67 |          * Unix timestamp for when the groupNotification was created
 68 |          * @type {number}
 69 |          */
 70 |         this.timestamp = data.t;
 71 | 
 72 |         /**
 73 |          * ID for the Chat that this groupNotification was sent for.
 74 |          * 
 75 |          * @type {string}
 76 |          */
 77 |         this.chatId = typeof (data.id.remote) === 'object' ? data.id.remote._serialized : data.id.remote;
 78 | 
 79 |         /**
 80 |          * ContactId for the user that produced the GroupNotification.
 81 |          * @type {string}
 82 |          */
 83 |         this.author = typeof (data.author) === 'object' ? data.author._serialized : data.author;
 84 |         
 85 |         /**
 86 |          * Contact IDs for the users that were affected by this GroupNotification.
 87 |          * @type {Array&lt;string>}
 88 |          */
 89 |         this.recipientIds = [];
 90 | 
 91 |         if (data.recipients) {
 92 |             this.recipientIds = data.recipients;
 93 |         }
 94 | 
 95 |         return super._patch(data);
 96 |     }
 97 | 
 98 |     /**
 99 |      * Returns the Chat this groupNotification was sent in
100 |      * @returns {Promise&lt;Chat>}
101 |      */
102 |     getChat() {
103 |         return this.client.getChatById(this.chatId);
104 |     }
105 | 
106 |     /**
107 |      * Returns the Contact this GroupNotification was produced by
108 |      * @returns {Promise&lt;Contact>}
109 |      */
110 |     getContact() {
111 |         return this.client.getContactById(this.author);
112 |     }
113 | 
114 |     /**
115 |      * Returns the Contacts affected by this GroupNotification.
116 |      * @returns {Promise&lt;Array&lt;Contact>>}
117 |      */
118 |     async getRecipients() {
119 |         return await Promise.all(this.recipientIds.map(async m => await this.client.getContactById(m)));
120 |     }
121 | 
122 |     /**
123 |      * Sends a message to the same chat this GroupNotification was produced in.
124 |      * 
125 |      * @param {string|MessageMedia|Location} content 
126 |      * @param {object} options
127 |      * @returns {Promise&lt;Message>}
128 |      */
129 |     async reply(content, options={}) {
130 |         return this.client.sendMessage(this.chatId, content, options);
131 |     }
132 |     
133 | }
134 | 
135 | module.exports = GroupNotification;
136 | 
137 |
138 |
139 |
140 | 141 |
142 |
143 |
144 | 149 |
150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /docs/LocalAuth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Class: LocalAuth 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 |
26 | 36 |
37 |
38 |
39 |

new LocalAuth(options)

40 |
41 |

Parameters

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 59 | 62 | 106 | 107 | 108 |
NameTypeOptionalDescription
54 |

options

55 |
57 |

 

58 |
60 |

 

61 |
63 |

options

64 |

Values in options have the following properties:

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 79 | 82 | 85 | 88 | 89 | 90 | 93 | 96 | 99 | 102 | 103 | 104 |
NameTypeOptionalDescription
77 |

clientId

78 |
80 |

 

81 |
83 |

 

84 |
86 |

Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance

87 |
91 |

dataPath

92 |
94 |

 

95 |
97 |

 

98 |
100 |

Change the default path for saving session files, default is: "./.wwebjs_auth/"

101 |
105 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | 118 |
119 |
120 |
121 | 126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [pedroslopez@me.com](mailto:pedroslopez@me.com). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /docs/util_InterfaceController.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: util/InterfaceController.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | /**
 35 |  * Interface Controller
 36 |  */
 37 | class InterfaceController {
 38 | 
 39 |     constructor(props) {
 40 |         this.pupPage = props.pupPage;
 41 |     }
 42 | 
 43 |     /**
 44 |      * Opens the Chat Window
 45 |      * @param {string} chatId ID of the chat window that will be opened
 46 |      */
 47 |     async openChatWindow(chatId) {
 48 |         await this.pupPage.evaluate(async chatId => {
 49 |             let chatWid = window.Store.WidFactory.createWid(chatId);
 50 |             let chat = await window.Store.Chat.find(chatWid);
 51 |             await window.Store.Cmd.openChatAt(chat);
 52 |         }, chatId);
 53 |     }
 54 | 
 55 |     /**
 56 |      * Opens the Chat Drawer
 57 |      * @param {string} chatId ID of the chat drawer that will be opened
 58 |      */
 59 |     async openChatDrawer(chatId) {
 60 |         await this.pupPage.evaluate(async chatId => {
 61 |             let chat = await window.Store.Chat.get(chatId);
 62 |             await window.Store.Cmd.openDrawerMid(chat);
 63 |         }, chatId);
 64 |     }
 65 | 
 66 |     /**
 67 |      * Opens the Chat Search
 68 |      * @param {string} chatId ID of the chat search that will be opened
 69 |      */
 70 |     async openChatSearch(chatId) {
 71 |         await this.pupPage.evaluate(async chatId => {
 72 |             let chat = await window.Store.Chat.get(chatId);
 73 |             await window.Store.Cmd.chatSearch(chat);
 74 |         }, chatId);
 75 |     }
 76 | 
 77 |     /**
 78 |      * Opens or Scrolls the Chat Window to the position of the message
 79 |      * @param {string} msgId ID of the message that will be scrolled to
 80 |      */
 81 |     async openChatWindowAt(msgId) {
 82 |         await this.pupPage.evaluate(async msgId => {
 83 |             let msg = await window.Store.Msg.get(msgId);
 84 |             await window.Store.Cmd.openChatAt(msg.chat, msg.chat.getSearchContext(msg));
 85 |         }, msgId);
 86 |     }
 87 | 
 88 |     /**
 89 |      * Opens the Message Drawer
 90 |      * @param {string} msgId ID of the message drawer that will be opened
 91 |      */
 92 |     async openMessageDrawer(msgId) {
 93 |         await this.pupPage.evaluate(async msgId => {
 94 |             let msg = await window.Store.Msg.get(msgId);
 95 |             await window.Store.Cmd.msgInfoDrawer(msg);
 96 |         }, msgId);
 97 |     }
 98 | 
 99 |     /**
100 |      * Closes the Right Drawer
101 |      */
102 |     async closeRightDrawer() {
103 |         await this.pupPage.evaluate(async () => {
104 |             await window.Store.Cmd.closeDrawerRight();
105 |         });
106 |     }
107 | 
108 |     /**
109 |      * Get all Features
110 |      */
111 |     async getFeatures() {
112 |         return await this.pupPage.evaluate(() => {
113 |             return window.Store.Features.F;
114 |         });
115 |     }
116 | 
117 |     /**
118 |      * Check if Feature is enabled
119 |      * @param {string} feature status to check
120 |      */
121 |     async checkFeatureStatus(feature) {
122 |         return await this.pupPage.evaluate((feature) => {
123 |             return window.Store.Features.supportsFeature(feature);
124 |         }, feature);
125 |     }
126 | 
127 |     /**
128 |      * Enable Features
129 |      * @param {string[]} features to be enabled
130 |      */
131 |     async enableFeatures(features) {
132 |         await this.pupPage.evaluate((features) => {
133 |             for (const feature in features) {
134 |                 window.Store.Features.setFeature(features[feature], true);
135 |             }
136 |         }, features);
137 |     }
138 | 
139 |     /**
140 |      * Disable Features
141 |      * @param {string[]} features to be disabled
142 |      */
143 |     async disableFeatures(features) {
144 |         await this.pupPage.evaluate((features) => {
145 |             for (const feature in features) {
146 |                 window.Store.Features.setFeature(features[feature], false);
147 |             }
148 |         }, features);
149 |     }
150 | }
151 | 
152 | module.exports = InterfaceController;
153 | 
154 |
155 |
156 |
157 | 158 |
159 |
160 |
161 | 166 |
167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /docs/Product.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Class: Product 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 |
26 | 36 |
37 |
38 |

Properties

39 |
40 |
41 |
42 |
currency
43 |
44 |
45 |
data
46 |
47 |
48 |
id
49 |
50 |
51 |
52 |
53 |
54 |
55 |
name
56 |
57 |
58 |
price
59 |
60 |
61 |
quantity
62 |
63 |
64 |
65 |
66 |
67 |
68 |
thumbnailUrl
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |

new Product()

78 |
79 |
Extends
80 |
Base
81 |
82 |
83 |
84 |

Properties

85 |
86 |

currency 87 |  string

88 |

Currency

89 |
90 |
91 |

data

92 |

Product metadata

93 |
94 |
95 |

id 96 |  string

97 |

Product ID

98 |
99 |
100 |

name 101 |  string

102 |

Product Name

103 |
104 |
105 |

price 106 |  string

107 |

Price

108 |
109 |
110 |

quantity 111 |  number

112 |

Product Quantity

113 |
114 |
115 |

thumbnailUrl 116 |  string

117 |

Product Thumbnail

118 |
119 |
120 |
121 |
122 |
123 |
124 | 125 |
126 |
127 |
128 | 133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/structures/Contact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Base = require('./Base'); 4 | 5 | /** 6 | * ID that represents a contact 7 | * @typedef {Object} ContactId 8 | * @property {string} server 9 | * @property {string} user 10 | * @property {string} _serialized 11 | */ 12 | 13 | /** 14 | * Represents a Contact on WhatsApp 15 | * @extends {Base} 16 | */ 17 | class Contact extends Base { 18 | constructor(client, data) { 19 | super(client); 20 | 21 | if(data) this._patch(data); 22 | } 23 | 24 | _patch(data) { 25 | /** 26 | * ID that represents the contact 27 | * @type {ContactId} 28 | */ 29 | this.id = data.id; 30 | 31 | /** 32 | * Contact's phone number 33 | * @type {string} 34 | */ 35 | this.number = data.userid; 36 | 37 | /** 38 | * Indicates if the contact is a business contact 39 | * @type {boolean} 40 | */ 41 | this.isBusiness = data.isBusiness; 42 | 43 | /** 44 | * Indicates if the contact is an enterprise contact 45 | * @type {boolean} 46 | */ 47 | this.isEnterprise = data.isEnterprise; 48 | 49 | this.labels = data.labels; 50 | 51 | /** 52 | * The contact's name, as saved by the current user 53 | * @type {?string} 54 | */ 55 | this.name = data.name; 56 | 57 | /** 58 | * The name that the contact has configured to be shown publically 59 | * @type {string} 60 | */ 61 | this.pushname = data.pushname; 62 | 63 | this.sectionHeader = data.sectionHeader; 64 | 65 | /** 66 | * A shortened version of name 67 | * @type {?string} 68 | */ 69 | this.shortName = data.shortName; 70 | 71 | this.statusMute = data.statusMute; 72 | this.type = data.type; 73 | this.verifiedLevel = data.verifiedLevel; 74 | this.verifiedName = data.verifiedName; 75 | 76 | /** 77 | * Indicates if the contact is the current user's contact 78 | * @type {boolean} 79 | */ 80 | this.isMe = data.isMe; 81 | 82 | /** 83 | * Indicates if the contact is a user contact 84 | * @type {boolean} 85 | */ 86 | this.isUser = data.isUser; 87 | 88 | /** 89 | * Indicates if the contact is a group contact 90 | * @type {boolean} 91 | */ 92 | this.isGroup = data.isGroup; 93 | 94 | /** 95 | * Indicates if the number is registered on WhatsApp 96 | * @type {boolean} 97 | */ 98 | this.isWAContact = data.isWAContact; 99 | 100 | /** 101 | * Indicates if the number is saved in the current phone's contacts 102 | * @type {boolean} 103 | */ 104 | this.isMyContact = data.isMyContact; 105 | 106 | /** 107 | * Indicates if you have blocked this contact 108 | * @type {boolean} 109 | */ 110 | this.isBlocked = data.isBlocked; 111 | 112 | return super._patch(data); 113 | } 114 | 115 | /** 116 | * Returns the contact's profile picture URL, if privacy settings allow it 117 | * @returns {Promise} 118 | */ 119 | async getProfilePicUrl() { 120 | return await this.client.getProfilePicUrl(this.id._serialized); 121 | } 122 | 123 | /** 124 | * Returns the contact's formatted phone number, (12345678901@c.us) => (+1 (234) 5678-901) 125 | * @returns {Promise} 126 | */ 127 | async getFormattedNumber() { 128 | return await this.client.getFormattedNumber(this.id._serialized); 129 | } 130 | 131 | /** 132 | * Returns the contact's countrycode, (1541859685@c.us) => (1) 133 | * @returns {Promise} 134 | */ 135 | async getCountryCode() { 136 | return await this.client.getCountryCode(this.id._serialized); 137 | } 138 | 139 | /** 140 | * Returns the Chat that corresponds to this Contact. 141 | * Will return null when getting chat for currently logged in user. 142 | * @returns {Promise} 143 | */ 144 | async getChat() { 145 | if(this.isMe) return null; 146 | 147 | return await this.client.getChatById(this.id._serialized); 148 | } 149 | 150 | /** 151 | * Blocks this contact from WhatsApp 152 | * @returns {Promise} 153 | */ 154 | async block() { 155 | if(this.isGroup) return false; 156 | 157 | await this.client.pupPage.evaluate(async (contactId) => { 158 | const contact = window.Store.Contact.get(contactId); 159 | await window.Store.BlockContact.blockContact(contact); 160 | }, this.id._serialized); 161 | 162 | return true; 163 | } 164 | 165 | /** 166 | * Unblocks this contact from WhatsApp 167 | * @returns {Promise} 168 | */ 169 | async unblock() { 170 | if(this.isGroup) return false; 171 | 172 | await this.client.pupPage.evaluate(async (contactId) => { 173 | const contact = window.Store.Contact.get(contactId); 174 | await window.Store.BlockContact.unblockContact(contact); 175 | }, this.id._serialized); 176 | 177 | return true; 178 | } 179 | 180 | /** 181 | * Gets the Contact's current "about" info. Returns null if you don't have permission to read their status. 182 | * @returns {Promise} 183 | */ 184 | async getAbout() { 185 | const about = await this.client.pupPage.evaluate(async (contactId) => { 186 | const wid = window.Store.WidFactory.createWid(contactId); 187 | return window.Store.StatusUtils.getStatus(wid); 188 | }, this.id._serialized); 189 | 190 | if (typeof about.status !== 'string') 191 | return null; 192 | 193 | return about.status; 194 | } 195 | 196 | /** 197 | * Gets the Contact's common groups with you. Returns empty array if you don't have any common group. 198 | * @returns {Promise} 199 | */ 200 | async getCommonGroups() { 201 | return await this.client.getCommonGroups(this.id._serialized); 202 | } 203 | 204 | } 205 | 206 | module.exports = Contact; 207 | -------------------------------------------------------------------------------- /docs/structures_MessageMedia.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: structures/MessageMedia.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | const fs = require('fs');
 35 | const path = require('path');
 36 | const mime = require('mime');
 37 | const fetch = require('node-fetch');
 38 | const { URL } = require('url');
 39 | 
 40 | /**
 41 |  * Media attached to a message
 42 |  * @param {string} mimetype MIME type of the attachment
 43 |  * @param {string} data Base64-encoded data of the file
 44 |  * @param {?string} filename Document file name
 45 |  */
 46 | class MessageMedia {
 47 |     constructor(mimetype, data, filename) {
 48 |         /**
 49 |          * MIME type of the attachment
 50 |          * @type {string}
 51 |          */
 52 |         this.mimetype = mimetype;
 53 | 
 54 |         /**
 55 |          * Base64 encoded data that represents the file
 56 |          * @type {string}
 57 |          */
 58 |         this.data = data;
 59 | 
 60 |         /**
 61 |          * Name of the file (for documents)
 62 |          * @type {?string}
 63 |          */
 64 |         this.filename = filename;
 65 |     }
 66 | 
 67 |     /**
 68 |      * Creates a MessageMedia instance from a local file path
 69 |      * @param {string} filePath 
 70 |      * @returns {MessageMedia}
 71 |      */
 72 |     static fromFilePath(filePath) {
 73 |         const b64data = fs.readFileSync(filePath, {encoding: 'base64'});
 74 |         const mimetype = mime.getType(filePath); 
 75 |         const filename = path.basename(filePath);
 76 | 
 77 |         return new MessageMedia(mimetype, b64data, filename);
 78 |     }
 79 | 
 80 |     /**
 81 |      * Creates a MessageMedia instance from a URL
 82 |      * @param {string} url
 83 |      * @param {Object} [options]
 84 |      * @param {boolean} [options.unsafeMime=false]
 85 |      * @param {string} [options.filename]
 86 |      * @param {object} [options.client]
 87 |      * @param {object} [options.reqOptions]
 88 |      * @param {number} [options.reqOptions.size=0]
 89 |      * @returns {Promise&lt;MessageMedia>}
 90 |      */
 91 |     static async fromUrl(url, options = {}) {
 92 |         const pUrl = new URL(url);
 93 |         let mimetype = mime.getType(pUrl.pathname);
 94 | 
 95 |         if (!mimetype &amp;&amp; !options.unsafeMime)
 96 |             throw new Error('Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.');
 97 | 
 98 |         async function fetchData (url, options) {
 99 |             const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options);
100 |             const response = await fetch(url, reqOptions);
101 |             const mime = response.headers.get('Content-Type');
102 | 
103 |             const contentDisposition = response.headers.get('Content-Disposition');
104 |             const name = contentDisposition ? contentDisposition.match(/((?&lt;=filename=")(.*)(?="))/) : null;
105 | 
106 |             let data = '';
107 |             if (response.buffer) {
108 |                 data = (await response.buffer()).toString('base64');
109 |             } else {
110 |                 const bArray = new Uint8Array(await response.arrayBuffer());
111 |                 bArray.forEach((b) => {
112 |                     data += String.fromCharCode(b);
113 |                 });
114 |                 data = btoa(data);
115 |             }
116 |             
117 |             return { data, mime, name };
118 |         }
119 | 
120 |         const res = options.client
121 |             ? (await options.client.pupPage.evaluate(fetchData, url, options.reqOptions))
122 |             : (await fetchData(url, options.reqOptions));
123 | 
124 |         const filename = options.filename ||
125 |             (res.name ? res.name[0] : (pUrl.pathname.split('/').pop() || 'file'));
126 |         
127 |         if (!mimetype)
128 |             mimetype = res.mime;
129 | 
130 |         return new MessageMedia(mimetype, res.data, filename);
131 |     }
132 | }
133 | 
134 | module.exports = MessageMedia;
135 | 
136 |
137 |
138 |
139 | 140 |
141 |
142 |
143 | 148 |
149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/Location.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Class: Location 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 |
26 | 36 |
37 |
38 |

Properties

39 |
40 |
41 |
42 |
description
43 |
44 |
45 |
46 |
47 |
48 |
49 |
latitude
50 |
51 |
52 |
53 |
54 |
55 |
56 |
longitude
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |

new Location(latitude, longitude, description)

66 |
67 |

Parameters

68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 82 | 85 | 88 | 90 | 91 | 92 | 95 | 98 | 101 | 103 | 104 | 105 | 108 | 111 | 114 | 117 | 118 | 119 |
NameTypeOptionalDescription
80 |

latitude

81 |
83 |

 

84 |
86 |

 

87 |
89 |
93 |

longitude

94 |
96 |

 

97 |
99 |

 

100 |
102 |
106 |

description

107 |
109 |

 

110 |
112 |

 

113 |
115 |

Value can be null.

116 |
120 |
121 |
122 |
123 |
124 |
125 |

Properties

126 |
127 |

description 128 |  nullable string

129 |

Name for the location

130 |
131 |
132 |

latitude 133 |  number

134 |

Location latitude

135 |
136 |
137 |

longitude 138 |  number

139 |

Location longitude

140 |
141 |
142 |
143 |
144 |
145 |
146 | 147 |
148 |
149 |
150 | 155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/util/Util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const Crypto = require('crypto'); 5 | const { tmpdir } = require('os'); 6 | const ffmpeg = require('fluent-ffmpeg'); 7 | const webp = require('node-webpmux'); 8 | const fs = require('fs').promises; 9 | 10 | const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); 11 | 12 | /** 13 | * Utility methods 14 | */ 15 | class Util { 16 | constructor() { 17 | throw new Error(`The ${this.constructor.name} class may not be instantiated.`); 18 | } 19 | 20 | static generateHash(length) { 21 | var result = ''; 22 | var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 23 | var charactersLength = characters.length; 24 | for (var i = 0; i < length; i++) { 25 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 26 | } 27 | return result; 28 | } 29 | 30 | /** 31 | * Sets default properties on an object that aren't already specified. 32 | * @param {Object} def Default properties 33 | * @param {Object} given Object to assign defaults to 34 | * @returns {Object} 35 | * @private 36 | */ 37 | static mergeDefault(def, given) { 38 | if (!given) return def; 39 | for (const key in def) { 40 | if (!has(given, key) || given[key] === undefined) { 41 | given[key] = def[key]; 42 | } else if (given[key] === Object(given[key])) { 43 | given[key] = Util.mergeDefault(def[key], given[key]); 44 | } 45 | } 46 | 47 | return given; 48 | } 49 | 50 | /** 51 | * Formats a image to webp 52 | * @param {MessageMedia} media 53 | * 54 | * @returns {Promise} media in webp format 55 | */ 56 | static async formatImageToWebpSticker(media, pupPage) { 57 | if (!media.mimetype.includes('image')) 58 | throw new Error('media is not a image'); 59 | 60 | if (media.mimetype.includes('webp')) { 61 | return media; 62 | } 63 | 64 | return pupPage.evaluate((media) => { 65 | return window.WWebJS.toStickerData(media); 66 | }, media); 67 | } 68 | 69 | /** 70 | * Formats a video to webp 71 | * @param {MessageMedia} media 72 | * 73 | * @returns {Promise} media in webp format 74 | */ 75 | static async formatVideoToWebpSticker(media) { 76 | if (!media.mimetype.includes('video')) 77 | throw new Error('media is not a video'); 78 | 79 | const videoType = media.mimetype.split('/')[1]; 80 | 81 | const tempFile = path.join( 82 | tmpdir(), 83 | `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp` 84 | ); 85 | 86 | const stream = new (require('stream').Readable)(); 87 | const buffer = Buffer.from( 88 | media.data.replace(`data:${media.mimetype};base64,`, ''), 89 | 'base64' 90 | ); 91 | stream.push(buffer); 92 | stream.push(null); 93 | 94 | await new Promise((resolve, reject) => { 95 | ffmpeg(stream) 96 | .inputFormat(videoType) 97 | .on('error', reject) 98 | .on('end', () => resolve(true)) 99 | .addOutputOptions([ 100 | '-vcodec', 101 | 'libwebp', 102 | '-vf', 103 | // eslint-disable-next-line no-useless-escape 104 | 'scale=\'iw*min(300/iw\,300/ih)\':\'ih*min(300/iw\,300/ih)\',format=rgba,pad=300:300:\'(300-iw)/2\':\'(300-ih)/2\':\'#00000000\',setsar=1,fps=10', 105 | '-loop', 106 | '0', 107 | '-ss', 108 | '00:00:00.0', 109 | '-t', 110 | '00:00:05.0', 111 | '-preset', 112 | 'default', 113 | '-an', 114 | '-vsync', 115 | '0', 116 | '-s', 117 | '512:512', 118 | ]) 119 | .toFormat('webp') 120 | .save(tempFile); 121 | }); 122 | 123 | const data = await fs.readFile(tempFile, 'base64'); 124 | await fs.unlink(tempFile); 125 | 126 | return { 127 | mimetype: 'image/webp', 128 | data: data, 129 | filename: media.filename, 130 | }; 131 | } 132 | 133 | /** 134 | * Sticker metadata. 135 | * @typedef {Object} StickerMetadata 136 | * @property {string} [name] 137 | * @property {string} [author] 138 | * @property {string[]} [categories] 139 | */ 140 | 141 | /** 142 | * Formats a media to webp 143 | * @param {MessageMedia} media 144 | * @param {StickerMetadata} metadata 145 | * 146 | * @returns {Promise} media in webp format 147 | */ 148 | static async formatToWebpSticker(media, metadata, pupPage) { 149 | let webpMedia; 150 | 151 | if (media.mimetype.includes('image')) 152 | webpMedia = await this.formatImageToWebpSticker(media, pupPage); 153 | else if (media.mimetype.includes('video')) 154 | webpMedia = await this.formatVideoToWebpSticker(media); 155 | else 156 | throw new Error('Invalid media format'); 157 | 158 | if (metadata.name || metadata.author) { 159 | const img = new webp.Image(); 160 | const hash = this.generateHash(32); 161 | const stickerPackId = hash; 162 | const packname = metadata.name; 163 | const author = metadata.author; 164 | const categories = metadata.categories || ['']; 165 | const json = { 'sticker-pack-id': stickerPackId, 'sticker-pack-name': packname, 'sticker-pack-publisher': author, 'emojis': categories }; 166 | let exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]); 167 | let jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8'); 168 | let exif = Buffer.concat([exifAttr, jsonBuffer]); 169 | exif.writeUIntLE(jsonBuffer.length, 14, 4); 170 | await img.load(Buffer.from(webpMedia.data, 'base64')); 171 | img.exif = exif; 172 | webpMedia.data = (await img.save(null)).toString('base64'); 173 | } 174 | 175 | return webpMedia; 176 | } 177 | 178 | /** 179 | * Configure ffmpeg path 180 | * @param {string} path 181 | */ 182 | static setFfmpegPath(path) { 183 | ffmpeg.setFfmpegPath(path); 184 | } 185 | } 186 | 187 | module.exports = Util; 188 | -------------------------------------------------------------------------------- /tests/structures/chat.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | 3 | const helper = require('../helper'); 4 | const Message = require('../../src/structures/Message'); 5 | const { MessageTypes } = require('../../src/util/Constants'); 6 | const { Contact } = require('../../src/structures'); 7 | 8 | const remoteId = helper.remoteId; 9 | 10 | describe('Chat', function () { 11 | let client; 12 | let chat; 13 | 14 | before(async function() { 15 | this.timeout(35000); 16 | client = helper.createClient({ authenticated: true }); 17 | await client.initialize(); 18 | chat = await client.getChatById(remoteId); 19 | }); 20 | 21 | after(async function () { 22 | await client.destroy(); 23 | }); 24 | 25 | it('can send a message to a chat', async function () { 26 | const msg = await chat.sendMessage('hello world'); 27 | expect(msg).to.be.instanceOf(Message); 28 | expect(msg.type).to.equal(MessageTypes.TEXT); 29 | expect(msg.fromMe).to.equal(true); 30 | expect(msg.body).to.equal('hello world'); 31 | expect(msg.to).to.equal(remoteId); 32 | }); 33 | 34 | it('can fetch messages sent in a chat', async function () { 35 | await helper.sleep(1000); 36 | const msg = await chat.sendMessage('another message'); 37 | await helper.sleep(500); 38 | 39 | const messages = await chat.fetchMessages(); 40 | expect(messages.length).to.be.greaterThanOrEqual(2); 41 | 42 | const fetchedMsg = messages[messages.length-1]; 43 | expect(fetchedMsg).to.be.instanceOf(Message); 44 | expect(fetchedMsg.type).to.equal(MessageTypes.TEXT); 45 | expect(fetchedMsg.id._serialized).to.equal(msg.id._serialized); 46 | expect(fetchedMsg.body).to.equal(msg.body); 47 | }); 48 | 49 | it('can use a limit when fetching messages sent in a chat', async function () { 50 | await helper.sleep(1000); 51 | const msg = await chat.sendMessage('yet another message'); 52 | await helper.sleep(500); 53 | 54 | const messages = await chat.fetchMessages({limit: 1}); 55 | expect(messages).to.have.lengthOf(1); 56 | 57 | const fetchedMsg = messages[0]; 58 | expect(fetchedMsg).to.be.instanceOf(Message); 59 | expect(fetchedMsg.type).to.equal(MessageTypes.TEXT); 60 | expect(fetchedMsg.id._serialized).to.equal(msg.id._serialized); 61 | expect(fetchedMsg.body).to.equal(msg.body); 62 | }); 63 | 64 | it('can get the related contact', async function () { 65 | const contact = await chat.getContact(); 66 | expect(contact).to.be.instanceOf(Contact); 67 | expect(contact.id._serialized).to.equal(chat.id._serialized); 68 | }); 69 | 70 | describe('Seen', function () { 71 | it('can mark a chat as unread', async function () { 72 | await chat.markUnread(); 73 | await helper.sleep(500); 74 | 75 | // refresh chat 76 | chat = await client.getChatById(remoteId); 77 | expect(chat.unreadCount).to.equal(-1); 78 | }); 79 | 80 | it('can mark a chat as seen', async function () { 81 | const res = await chat.sendSeen(); 82 | expect(res).to.equal(true); 83 | 84 | await helper.sleep(1000); 85 | 86 | // refresh chat 87 | chat = await client.getChatById(remoteId); 88 | expect(chat.unreadCount).to.equal(0); 89 | }); 90 | }); 91 | 92 | describe('Archiving', function (){ 93 | it('can archive a chat', async function () { 94 | const res = await chat.archive(); 95 | expect(res).to.equal(true); 96 | 97 | await helper.sleep(1000); 98 | 99 | // refresh chat 100 | chat = await client.getChatById(remoteId); 101 | expect(chat.archived).to.equal(true); 102 | }); 103 | 104 | it('can unarchive a chat', async function () { 105 | const res = await chat.unarchive(); 106 | expect(res).to.equal(false); 107 | 108 | await helper.sleep(1000); 109 | 110 | // refresh chat 111 | chat = await client.getChatById(remoteId); 112 | expect(chat.archived).to.equal(false); 113 | }); 114 | }); 115 | 116 | describe('Pinning', function () { 117 | it('can pin a chat', async function () { 118 | const res = await chat.pin(); 119 | expect(res).to.equal(true); 120 | 121 | await helper.sleep(1000); 122 | 123 | // refresh chat 124 | chat = await client.getChatById(remoteId); 125 | expect(chat.pinned).to.equal(true); 126 | }); 127 | 128 | it('can unpin a chat', async function () { 129 | const res = await chat.unpin(); 130 | expect(res).to.equal(false); 131 | await helper.sleep(1000); 132 | 133 | // refresh chat 134 | chat = await client.getChatById(remoteId); 135 | expect(chat.pinned).to.equal(false); 136 | }); 137 | }); 138 | 139 | describe('Muting', function () { 140 | it('can mute a chat forever', async function() { 141 | await chat.mute(); 142 | 143 | await helper.sleep(1000); 144 | 145 | // refresh chat 146 | chat = await client.getChatById(remoteId); 147 | expect(chat.isMuted).to.equal(true); 148 | expect(chat.muteExpiration).to.equal(-1); 149 | }); 150 | 151 | it('can mute a chat until a specific date', async function() { 152 | const unmuteDate = new Date(new Date().getTime() + (1000*60*60)); 153 | await chat.mute(unmuteDate); 154 | 155 | await helper.sleep(1000); 156 | 157 | // refresh chat 158 | chat = await client.getChatById(remoteId); 159 | expect(chat.isMuted).to.equal(true); 160 | expect(chat.muteExpiration).to.equal( 161 | Math.round(unmuteDate.getTime() / 1000) 162 | ); 163 | }); 164 | 165 | it('can unmute a chat', async function () { 166 | await chat.unmute(); 167 | await helper.sleep(500); 168 | 169 | // refresh chat 170 | chat = await client.getChatById(remoteId); 171 | expect(chat.isMuted).to.equal(false); 172 | expect(chat.muteExpiration).to.equal(0); 173 | }); 174 | }); 175 | 176 | // eslint-disable-next-line mocha/no-skipped-tests 177 | describe.skip('Destructive operations', function () { 178 | it('can clear all messages from chat', async function () { 179 | const res = await chat.clearMessages(); 180 | expect(res).to.equal(true); 181 | 182 | await helper.sleep(3000); 183 | 184 | const msgs = await chat.fetchMessages(); 185 | expect(msgs).to.have.lengthOf(0); 186 | }); 187 | 188 | it('can delete a chat', async function () { 189 | const res = await chat.delete(); 190 | expect(res).to.equal(true); 191 | }); 192 | }); 193 | }); -------------------------------------------------------------------------------- /docs/util_Constants.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | whatsapp-web.js 1.16.6 » Source: util/Constants.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 |
24 |
25 | 27 |
28 | 31 |
32 |
'use strict';
 33 | 
 34 | exports.WhatsWebURL = 'https://web.whatsapp.com/';
 35 | 
 36 | exports.DefaultOptions = {
 37 |     puppeteer: {
 38 |         headless: true,
 39 |         defaultViewport: null
 40 |     },
 41 |     authTimeoutMs: 0,
 42 |     qrMaxRetries: 0,
 43 |     takeoverOnConflict: false,
 44 |     takeoverTimeoutMs: 0,
 45 |     userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
 46 |     ffmpegPath: 'ffmpeg',
 47 |     bypassCSP: false
 48 | };
 49 | 
 50 | /**
 51 |  * Client status
 52 |  * @readonly
 53 |  * @enum {number}
 54 |  */
 55 | exports.Status = {
 56 |     INITIALIZING: 0,
 57 |     AUTHENTICATING: 1,
 58 |     READY: 3
 59 | };
 60 | 
 61 | /**
 62 |  * Events that can be emitted by the client
 63 |  * @readonly
 64 |  * @enum {string}
 65 |  */
 66 | exports.Events = {
 67 |     AUTHENTICATED: 'authenticated',
 68 |     AUTHENTICATION_FAILURE: 'auth_failure',
 69 |     READY: 'ready',
 70 |     MESSAGE_RECEIVED: 'message',
 71 |     MESSAGE_CREATE: 'message_create',
 72 |     MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone',
 73 |     MESSAGE_REVOKED_ME: 'message_revoke_me',
 74 |     MESSAGE_ACK: 'message_ack',
 75 |     MEDIA_UPLOADED: 'media_uploaded',
 76 |     GROUP_JOIN: 'group_join',
 77 |     GROUP_LEAVE: 'group_leave',
 78 |     GROUP_UPDATE: 'group_update',
 79 |     QR_RECEIVED: 'qr',
 80 |     DISCONNECTED: 'disconnected',
 81 |     STATE_CHANGED: 'change_state',
 82 |     BATTERY_CHANGED: 'change_battery',
 83 |     INCOMING_CALL: 'incoming_call'
 84 | };
 85 | 
 86 | /**
 87 |  * Message types
 88 |  * @readonly
 89 |  * @enum {string}
 90 |  */
 91 | exports.MessageTypes = {
 92 |     TEXT: 'chat',
 93 |     AUDIO: 'audio',
 94 |     VOICE: 'ptt',
 95 |     IMAGE: 'image',
 96 |     VIDEO: 'video',
 97 |     DOCUMENT: 'document',
 98 |     STICKER: 'sticker',
 99 |     LOCATION: 'location',
100 |     CONTACT_CARD: 'vcard',
101 |     CONTACT_CARD_MULTI: 'multi_vcard',
102 |     ORDER: 'order',
103 |     REVOKED: 'revoked',
104 |     PRODUCT: 'product',
105 |     UNKNOWN: 'unknown',
106 |     GROUP_INVITE: 'groups_v4_invite',
107 |     LIST: 'list',
108 |     LIST_RESPONSE: 'list_response',
109 |     BUTTONS_RESPONSE: 'buttons_response',
110 |     PAYMENT: 'payment',
111 |     BROADCAST_NOTIFICATION: 'broadcast_notification',
112 |     CALL_LOG: 'call_log',
113 |     CIPHERTEXT: 'ciphertext',
114 |     DEBUG: 'debug',
115 |     E2E_NOTIFICATION: 'e2e_notification',
116 |     GP2: 'gp2',
117 |     GROUP_NOTIFICATION: 'group_notification',
118 |     HSM: 'hsm',
119 |     INTERACTIVE: 'interactive',
120 |     NATIVE_FLOW: 'native_flow',
121 |     NOTIFICATION: 'notification',
122 |     NOTIFICATION_TEMPLATE: 'notification_template',
123 |     OVERSIZED: 'oversized',
124 |     PROTOCOL: 'protocol',
125 |     REACTION: 'reaction',
126 |     TEMPLATE_BUTTON_REPLY: 'template_button_reply',
127 | };
128 | 
129 | /**
130 |  * Group notification types
131 |  * @readonly
132 |  * @enum {string}
133 |  */
134 | exports.GroupNotificationTypes = {
135 |     ADD: 'add',
136 |     INVITE: 'invite',
137 |     REMOVE: 'remove',
138 |     LEAVE: 'leave',
139 |     SUBJECT: 'subject',
140 |     DESCRIPTION: 'description',
141 |     PICTURE: 'picture',
142 |     ANNOUNCE: 'announce',
143 |     RESTRICT: 'restrict',
144 | };
145 | 
146 | /**
147 |  * Chat types
148 |  * @readonly
149 |  * @enum {string}
150 |  */
151 | exports.ChatTypes = {
152 |     SOLO: 'solo',
153 |     GROUP: 'group',
154 |     UNKNOWN: 'unknown'
155 | };
156 | 
157 | /**
158 |  * WhatsApp state
159 |  * @readonly
160 |  * @enum {string}
161 |  */
162 | exports.WAState = {
163 |     CONFLICT: 'CONFLICT',
164 |     CONNECTED: 'CONNECTED',
165 |     DEPRECATED_VERSION: 'DEPRECATED_VERSION',
166 |     OPENING: 'OPENING',
167 |     PAIRING: 'PAIRING',
168 |     PROXYBLOCK: 'PROXYBLOCK',
169 |     SMB_TOS_BLOCK: 'SMB_TOS_BLOCK',
170 |     TIMEOUT: 'TIMEOUT',
171 |     TOS_BLOCK: 'TOS_BLOCK',
172 |     UNLAUNCHED: 'UNLAUNCHED',
173 |     UNPAIRED: 'UNPAIRED',
174 |     UNPAIRED_IDLE: 'UNPAIRED_IDLE'
175 | };
176 | 
177 | /**
178 |  * Message ACK
179 |  * @readonly
180 |  * @enum {number}
181 |  */
182 | exports.MessageAck = {
183 |     ACK_ERROR: -1,
184 |     ACK_PENDING: 0,
185 |     ACK_SERVER: 1,
186 |     ACK_DEVICE: 2,
187 |     ACK_READ: 3,
188 |     ACK_PLAYED: 4,
189 | };
190 | 
191 |
192 |
193 |
194 | 195 |
196 |
197 |
198 | 203 |
204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | --------------------------------------------------------------------------------