From a03cc41d222d35a51bcdd90c7d8a9fd870f315d2 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Fri, 16 Jul 2021 02:50:05 -0400 Subject: [PATCH] Add tests and detect WhatsApp Web updates (#686) * test setup, add initializer tests * test sending messages * add script to check latest version * add github action * use env vars * configure environment with .env file * add test for sticker name and author * add DownloadManager model * test chats and contacts * test for number utility functions * throw error if no remote id has been set * Update .version --- .env.example | 2 + .eslintrc.json | 3 +- .github/workflows/update.yml | 35 +++ package.json | 9 +- src/util/Injected.js | 8 +- tests/README.md | 10 + tests/client.js | 417 +++++++++++++++++++++++++++ tests/helper.js | 40 +++ tools/version-checker/.version | 1 + tools/version-checker/update-version | 55 ++++ 10 files changed, 574 insertions(+), 6 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/update.yml create mode 100644 tests/README.md create mode 100644 tests/client.js create mode 100644 tests/helper.js create mode 100644 tools/version-checker/.version create mode 100755 tools/version-checker/update-version diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..6cd9eb9a83 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +WWEBJS_TEST_SESSION_PATH=test_session.json +WWEBJS_TEST_REMOTE_ID=XXXXXXXXXX@c.us \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 1fb052af6f..4e1f4fdeb7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@ "es6": true, "node": true }, - "extends": "eslint:recommended", + "extends": ["eslint:recommended", "plugin:mocha/recommended"], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" @@ -13,6 +13,7 @@ "parserOptions": { "ecmaVersion": 2020 }, + "plugins": ["mocha"], "ignorePatterns": ["docs"], "rules": { "indent": [ diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml new file mode 100644 index 0000000000..31bfaf163e --- /dev/null +++ b/.github/workflows/update.yml @@ -0,0 +1,35 @@ +name: Update + +on: + schedule: + - cron: "0/15 * * * *" + workflow_dispatch: + +jobs: + update: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install node v14 + uses: actions/setup-node@v2 + with: + node-version: '14' + - name: Install dependencies + run: npm install + - run: cd ./tools/version-checker + - name: Run Updater + run: ./update-version + - name: Store WA Version + run: echo WA_VERSION=`cat ./.version` >> $GITHUB_ENV + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + commit-message: Update supported WhatsApp Web version to v${{ env.WA_VERSION }} + title: Update WhatsApp Web Version (${{ env.WA_VERSION }}) + body: | + A new version of WhatsApp Web has been detected! + + Tests should be run against this new version before merging. + labels: WhatsApp Change + reviewers: pedroslopez \ No newline at end of file diff --git a/package.json b/package.json index 85bfb32846..57dd44b205 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "./index.js", "typings": "./index.d.ts", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "mocha tests", "shell": "node --experimental-repl-await ./shell.js", "generate-docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose" }, @@ -37,8 +37,13 @@ "sharp": "^0.28.3" }, "devDependencies": { + "chai": "^4.3.4", + "dotenv": "^10.0.0", "eslint": "^7.27.0", + "eslint-plugin-mocha": "^9.0.0", "jsdoc": "^3.6.4", - "jsdoc-baseline": "^0.1.5" + "jsdoc-baseline": "^0.1.5", + "mocha": "^8.4.0", + "sinon": "^11.1.1" } } diff --git a/src/util/Injected.js b/src/util/Injected.js index c5150ba0d3..c2a83580dc 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -294,7 +294,8 @@ exports.LoadUtils = () => { }; window.WWebJS.getChat = async chatId => { - const chat = window.Store.Chat.get(chatId); + const chatWid = window.Store.WidFactory.createWid(chatId); + const chat = await window.Store.Chat.find(chatWid); return await window.WWebJS.getChatModel(chat); }; @@ -324,8 +325,9 @@ exports.LoadUtils = () => { return res; }; - window.WWebJS.getContact = contactId => { - const contact = window.Store.Contact.get(contactId); + window.WWebJS.getContact = async contactId => { + const wid = window.Store.WidFactory.createWid(contactId); + const contact = await window.Store.Contact.find(wid); return window.WWebJS.getContactModel(contact); }; diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..cf53bf1069 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,10 @@ +## Running tests + +These tests require an authenticated WhatsApp Web session, as well as an additional phone that you can send messages to. + +This can be configured using the following environment variables: +- `WWEBJS_TEST_SESSION`: A JSON-formatted string with the session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. +- `WWEBJS_TEST_SESSION_PATH`: Path to a JSON file that contains the session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`. +- `WWEBJS_TEST_REMOTEID`: A valid WhatsApp ID that you can send messages to, e.g. `123456789@c.us`. + +You *must* set `WWEBJS_TEST_REMOTEID` **and** either `WWEBJS_TEST_SESSION` or `WWEBJS_TEST_SESSION_PATH` for the tests to run properly. \ No newline at end of file diff --git a/tests/client.js b/tests/client.js new file mode 100644 index 0000000000..6055c18bf5 --- /dev/null +++ b/tests/client.js @@ -0,0 +1,417 @@ +const {expect} = require('chai'); +const sinon = require('sinon'); + +const helper = require('./helper'); +const Chat = require('../src/structures/Chat'); +const Contact = require('../src/structures/Contact'); +const Message = require('../src/structures/Message'); +const MessageMedia = require('../src/structures/MessageMedia'); +const Location = require('../src/structures/Location'); +const { MessageTypes } = require('../src/util/Constants'); + +const remoteId = helper.remoteId; + +describe('Client', function() { + describe('Authentication', function() { + it('should emit QR code if not authenticated', async function() { + this.timeout(25000); + const callback = sinon.spy(); + + const client = helper.createClient(); + client.on('qr', callback); + client.initialize(); + + await helper.sleep(20000); + + expect(callback.called).to.equal(true); + expect(callback.args[0][0]).to.have.lengthOf(152); + + await client.destroy(); + }); + + it('should fail auth if session is invalid', async function() { + this.timeout(40000); + + const authFailCallback = sinon.spy(); + const qrCallback = sinon.spy(); + const readyCallback = sinon.spy(); + + const client = helper.createClient({ + options: { + session: { + WABrowserId: 'invalid', + WASecretBundle: 'invalid', + WAToken1: 'invalid', + WAToken2: 'invalid' + }, + authTimeoutMs: 10000, + restartOnAuthFail: false + } + }); + + client.on('qr', qrCallback); + client.on('auth_failure', authFailCallback); + client.on('ready', readyCallback); + + client.initialize(); + + await helper.sleep(25000); + + expect(authFailCallback.called).to.equal(true); + expect(authFailCallback.args[0][0]).to.equal('Unable to log in. Are the session details valid?'); + + expect(readyCallback.called).to.equal(false); + expect(qrCallback.called).to.equal(false); + + await client.destroy(); + }); + + it('can restart without a session if session was invalid and restartOnAuthFail=true', async function() { + this.timeout(40000); + + const authFailCallback = sinon.spy(); + const qrCallback = sinon.spy(); + + const client = helper.createClient({ + options:{ + session: { + WABrowserId: 'invalid', + WASecretBundle: 'invalid', + WAToken1: 'invalid', + WAToken2: 'invalid' + }, + authTimeoutMs: 10000, + restartOnAuthFail: true + } + }); + + client.on('auth_failure', authFailCallback); + client.on('qr', qrCallback); + + client.initialize(); + + await helper.sleep(35000); + + expect(authFailCallback.called).to.equal(true); + expect(qrCallback.called).to.equal(true); + expect(qrCallback.args[0][0]).to.have.lengthOf(152); + + await client.destroy(); + }); + + it('should authenticate with existing session', async function() { + this.timeout(40000); + + const authenticatedCallback = sinon.spy(); + const qrCallback = sinon.spy(); + const readyCallback = sinon.spy(); + + const client = helper.createClient({withSession: true}); + client.on('qr', qrCallback); + client.on('authenticated', authenticatedCallback); + client.on('ready', readyCallback); + + await client.initialize(); + + expect(authenticatedCallback.called).to.equal(true); + const newSession = authenticatedCallback.args[0][0]; + expect(newSession).to.have.key([ + 'WABrowserId', + 'WASecretBundle', + 'WAToken1', + 'WAToken2' + ]); + expect(authenticatedCallback.called).to.equal(true); + expect(readyCallback.called).to.equal(true); + expect(qrCallback.called).to.equal(false); + + await client.destroy(); + }); + }); + + describe('Authenticated', function() { + let client; + + before(async function() { + this.timeout(35000); + client = helper.createClient({withSession: true}); + await client.initialize(); + }); + + after(async function () { + await client.destroy(); + }); + + describe('Expose Store', function() { + it('exposes the store', async function() { + const exposed = await client.pupPage.evaluate(() => { + return Boolean(window.Store); + }); + + expect(exposed).to.equal(true); + }); + + it('exposes all required WhatsApp Web internal models', async function() { + const expectedModules = [ + 'Chat', + 'Msg', + 'Contact', + 'Conn', + 'AppState', + 'CryptoLib', + 'Wap', + 'SendSeen', + 'SendClear', + 'SendDelete', + 'genId', + 'SendMessage', + 'MsgKey', + 'Invite', + 'OpaqueData', + 'MediaPrep', + 'MediaObject', + 'MediaUpload', + 'Cmd', + 'MediaTypes', + 'VCard', + 'UserConstructor', + 'Validators', + 'WidFactory', + 'BlockContact', + 'GroupMetadata', + 'Sticker', + 'UploadUtils', + 'Label', + 'Features', + 'QueryOrder', + 'QueryProduct', + 'DownloadManager' + ]; + + const loadedModules = await client.pupPage.evaluate(() => { + return Object.keys(window.Store); + }); + + expect(loadedModules).to.include.members(expectedModules); + }); + }); + + describe('Send Messages', function () { + it('can send a message', async function() { + const msg = await client.sendMessage(remoteId, 'hello world'); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.TEXT); + expect(msg.fromMe).to.equal(true); + expect(msg.body).to.equal('hello world'); + expect(msg.to).to.equal(remoteId); + }); + + it('can send a media message', async function() { + const media = new MessageMedia( + 'image/png', + 'iVBORw0KGgoAAAANSUhEUgAAAV4AAACWBAMAAABkyf1EAAAAG1BMVEXMzMyWlpacnJyqqqrFxcWxsbGjo6O3t7e+vr6He3KoAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEcElEQVR4nO2aTW/bRhCGh18ij1zKknMkbbf2UXITIEeyMhIfRaF1exQLA/JRclslRykO+rs7s7s0VwytNmhJtsA8gHZEcox9PTs7uysQgGEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYr2OWRK/ReIKI8Zt7Hb19wTcQ0uTkGh13bQupcw7gPOvdo12/5CzNtNR7xLUtNtT3CGBQ6g3InjY720pvofUec22LJPr8PhEp2OMPyI40PdwWUdronCu9yQpdPx53bQlfLKnfOVhlnDYRBXve4Ov+IZTeMgdedm0NR+xoXJeQvdJ3CvziykSukwil16W/Oe7aGjIjqc/9ib4jQlJy0uArtN4A0+cvXFvDkmUJ47sJ1Y1ATLDNVXZkNPIepQzxy1ki9fqiwbUj/I+64zxWNzyZnPuhvohJ9K70VvXBixpcu2SAHU+Xd9EKdEJDNpYP3AQr3bQSpPQ6Y6/4dl1z7ZDbArsszjA7L0g7ibB0CDcidUWVoErvIMKZh2Xs0LUzcLW6V5NfiUgNEbaYmAVL6bXl0nJRc+1S72ua/D/cTjGPlQj7eUqd7A096rYlRjdPYlhz7VIvxpVG3cemDKF+WAwLY/6XelOZKTXXzsC4xvDjjtSN6kHLhLke6PrwM8h1raf40qjrGO7H9aTEbduucjS04ZrYU/4iuS5Z2Hdt0rvCLFdmLEXcU30AGddST62o+sLcf5l6k7CP+ru4pLYqX/VFyxbm/utQbx/r22ZEbTb2f5I2kns1Y1OQR8ZyofX+TjJxj1Rz7QQVnf1QzR26Oth0ueJVYcRP6ZUPac/Rx/5M6ixO1dhSrT3Y1DpiYmx3tF4ZUdpz9LD/dSg9PXES0LB71BwcGjKROuV28lnvnv7HHJsezheBGH5+X2CfSfRbMKW+5aGs3JFjMrjGibJc0S7TJzqjHrh2hDybj9XRXNZa89Aro55XBdbW5wti2c/5WJ7jJ1RolVUn/HWpb0I58Tziup6Rx7Dm2hnbRP1GM9PW/NFmQ4PtVRVN63Wvxfmu5sowDMMwDMMwDMMwDMMwDMMwDMMwzL+CpT//F/6beoV8zb2Jmt4Qryx6lTUCsENQ75HOkhXAO3EPVgyQtKtUy3C/e+FJg17Zjnew1Xrdb9InbG4WqfUAftG+WhLwPVyfg536+MU7m4C1CMk4ZznpXZzDYI1PDL2nS1hpvc5cNd7E2sJg05Fe7/7d3Fln8Cvc3bwB616auxsKl4WPghjemHrDqyDWeu1UNW5s2btPnSQ75oOdunEwWazfwgVG0kqluYCM9OIjWOGnfA2b9G4Ha63XKpvQ8perTvTifJNhi6+WMWmi7smEZf6G8MmhlyGq+NqP8GV84TLuJr7UIQVx+bDEoEpRZIz42gs40OuN4Mv8hXzelV7KX1isH+ewTWckikyVv+CfHuqVF7I16gN0VKypX6wPsE+zFPzkinolU9UH8OMGvSpnZqKsv13p/RsMun6X5x/y2LeAr8O66lsBwzBMP/wJfyGq8pgBk6IAAAAASUVORK5CYII=' + ); + + const msg = await client.sendMessage(remoteId, media, {caption: 'here\'s my media'}); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.IMAGE); + expect(msg.fromMe).to.equal(true); + expect(msg.hasMedia).to.equal(true); + expect(msg.body).to.equal('here\'s my media'); + expect(msg.to).to.equal(remoteId); + }); + + it('can send a media message as a document', async function() { + const media = new MessageMedia( + 'image/png', + 'iVBORw0KGgoAAAANSUhEUgAAAV4AAACWBAMAAABkyf1EAAAAG1BMVEXMzMyWlpacnJyqqqrFxcWxsbGjo6O3t7e+vr6He3KoAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEcElEQVR4nO2aTW/bRhCGh18ij1zKknMkbbf2UXITIEeyMhIfRaF1exQLA/JRclslRykO+rs7s7s0VwytNmhJtsA8gHZEcox9PTs7uysQgGEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYr2OWRK/ReIKI8Zt7Hb19wTcQ0uTkGh13bQupcw7gPOvdo12/5CzNtNR7xLUtNtT3CGBQ6g3InjY720pvofUec22LJPr8PhEp2OMPyI40PdwWUdronCu9yQpdPx53bQlfLKnfOVhlnDYRBXve4Ov+IZTeMgdedm0NR+xoXJeQvdJ3CvziykSukwil16W/Oe7aGjIjqc/9ib4jQlJy0uArtN4A0+cvXFvDkmUJ47sJ1Y1ATLDNVXZkNPIepQzxy1ki9fqiwbUj/I+64zxWNzyZnPuhvohJ9K70VvXBixpcu2SAHU+Xd9EKdEJDNpYP3AQr3bQSpPQ6Y6/4dl1z7ZDbArsszjA7L0g7ibB0CDcidUWVoErvIMKZh2Xs0LUzcLW6V5NfiUgNEbaYmAVL6bXl0nJRc+1S72ua/D/cTjGPlQj7eUqd7A096rYlRjdPYlhz7VIvxpVG3cemDKF+WAwLY/6XelOZKTXXzsC4xvDjjtSN6kHLhLke6PrwM8h1raf40qjrGO7H9aTEbduucjS04ZrYU/4iuS5Z2Hdt0rvCLFdmLEXcU30AGddST62o+sLcf5l6k7CP+ru4pLYqX/VFyxbm/utQbx/r22ZEbTb2f5I2kns1Y1OQR8ZyofX+TjJxj1Rz7QQVnf1QzR26Oth0ueJVYcRP6ZUPac/Rx/5M6ixO1dhSrT3Y1DpiYmx3tF4ZUdpz9LD/dSg9PXES0LB71BwcGjKROuV28lnvnv7HHJsezheBGH5+X2CfSfRbMKW+5aGs3JFjMrjGibJc0S7TJzqjHrh2hDybj9XRXNZa89Aro55XBdbW5wti2c/5WJ7jJ1RolVUn/HWpb0I58Tziup6Rx7Dm2hnbRP1GM9PW/NFmQ4PtVRVN63Wvxfmu5sowDMMwDMMwDMMwDMMwDMMwDMMwzL+CpT//F/6beoV8zb2Jmt4Qryx6lTUCsENQ75HOkhXAO3EPVgyQtKtUy3C/e+FJg17Zjnew1Xrdb9InbG4WqfUAftG+WhLwPVyfg536+MU7m4C1CMk4ZznpXZzDYI1PDL2nS1hpvc5cNd7E2sJg05Fe7/7d3Fln8Cvc3bwB616auxsKl4WPghjemHrDqyDWeu1UNW5s2btPnSQ75oOdunEwWazfwgVG0kqluYCM9OIjWOGnfA2b9G4Ha63XKpvQ8perTvTifJNhi6+WMWmi7smEZf6G8MmhlyGq+NqP8GV84TLuJr7UIQVx+bDEoEpRZIz42gs40OuN4Mv8hXzelV7KX1isH+ewTWckikyVv+CfHuqVF7I16gN0VKypX6wPsE+zFPzkinolU9UH8OMGvSpnZqKsv13p/RsMun6X5x/y2LeAr8O66lsBwzBMP/wJfyGq8pgBk6IAAAAASUVORK5CYII=', + 'this is my filename.png' + ); + + const msg = await client.sendMessage(remoteId, media, { sendMediaAsDocument: true}); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.DOCUMENT); + expect(msg.fromMe).to.equal(true); + expect(msg.hasMedia).to.equal(true); + expect(msg.body).to.equal('this is my filename.png'); + expect(msg.to).to.equal(remoteId); + }); + + it('can send a sticker message', async function() { + const media = new MessageMedia( + 'image/png', + 'iVBORw0KGgoAAAANSUhEUgAAAV4AAACWBAMAAABkyf1EAAAAG1BMVEXMzMyWlpacnJyqqqrFxcWxsbGjo6O3t7e+vr6He3KoAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEcElEQVR4nO2aTW/bRhCGh18ij1zKknMkbbf2UXITIEeyMhIfRaF1exQLA/JRclslRykO+rs7s7s0VwytNmhJtsA8gHZEcox9PTs7uysQgGEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYr2OWRK/ReIKI8Zt7Hb19wTcQ0uTkGh13bQupcw7gPOvdo12/5CzNtNR7xLUtNtT3CGBQ6g3InjY720pvofUec22LJPr8PhEp2OMPyI40PdwWUdronCu9yQpdPx53bQlfLKnfOVhlnDYRBXve4Ov+IZTeMgdedm0NR+xoXJeQvdJ3CvziykSukwil16W/Oe7aGjIjqc/9ib4jQlJy0uArtN4A0+cvXFvDkmUJ47sJ1Y1ATLDNVXZkNPIepQzxy1ki9fqiwbUj/I+64zxWNzyZnPuhvohJ9K70VvXBixpcu2SAHU+Xd9EKdEJDNpYP3AQr3bQSpPQ6Y6/4dl1z7ZDbArsszjA7L0g7ibB0CDcidUWVoErvIMKZh2Xs0LUzcLW6V5NfiUgNEbaYmAVL6bXl0nJRc+1S72ua/D/cTjGPlQj7eUqd7A096rYlRjdPYlhz7VIvxpVG3cemDKF+WAwLY/6XelOZKTXXzsC4xvDjjtSN6kHLhLke6PrwM8h1raf40qjrGO7H9aTEbduucjS04ZrYU/4iuS5Z2Hdt0rvCLFdmLEXcU30AGddST62o+sLcf5l6k7CP+ru4pLYqX/VFyxbm/utQbx/r22ZEbTb2f5I2kns1Y1OQR8ZyofX+TjJxj1Rz7QQVnf1QzR26Oth0ueJVYcRP6ZUPac/Rx/5M6ixO1dhSrT3Y1DpiYmx3tF4ZUdpz9LD/dSg9PXES0LB71BwcGjKROuV28lnvnv7HHJsezheBGH5+X2CfSfRbMKW+5aGs3JFjMrjGibJc0S7TJzqjHrh2hDybj9XRXNZa89Aro55XBdbW5wti2c/5WJ7jJ1RolVUn/HWpb0I58Tziup6Rx7Dm2hnbRP1GM9PW/NFmQ4PtVRVN63Wvxfmu5sowDMMwDMMwDMMwDMMwDMMwDMMwzL+CpT//F/6beoV8zb2Jmt4Qryx6lTUCsENQ75HOkhXAO3EPVgyQtKtUy3C/e+FJg17Zjnew1Xrdb9InbG4WqfUAftG+WhLwPVyfg536+MU7m4C1CMk4ZznpXZzDYI1PDL2nS1hpvc5cNd7E2sJg05Fe7/7d3Fln8Cvc3bwB616auxsKl4WPghjemHrDqyDWeu1UNW5s2btPnSQ75oOdunEwWazfwgVG0kqluYCM9OIjWOGnfA2b9G4Ha63XKpvQ8perTvTifJNhi6+WMWmi7smEZf6G8MmhlyGq+NqP8GV84TLuJr7UIQVx+bDEoEpRZIz42gs40OuN4Mv8hXzelV7KX1isH+ewTWckikyVv+CfHuqVF7I16gN0VKypX6wPsE+zFPzkinolU9UH8OMGvSpnZqKsv13p/RsMun6X5x/y2LeAr8O66lsBwzBMP/wJfyGq8pgBk6IAAAAASUVORK5CYII=' + ); + + const msg = await client.sendMessage(remoteId, media, {sendMediaAsSticker: true}); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.STICKER); + expect(msg.fromMe).to.equal(true); + expect(msg.hasMedia).to.equal(true); + expect(msg.to).to.equal(remoteId); + }); + + it('can send a sticker message with custom author and name', async function() { + const media = new MessageMedia( + 'image/png', + 'iVBORw0KGgoAAAANSUhEUgAAAV4AAACWBAMAAABkyf1EAAAAG1BMVEXMzMyWlpacnJyqqqrFxcWxsbGjo6O3t7e+vr6He3KoAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEcElEQVR4nO2aTW/bRhCGh18ij1zKknMkbbf2UXITIEeyMhIfRaF1exQLA/JRclslRykO+rs7s7s0VwytNmhJtsA8gHZEcox9PTs7uysQgGEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYr2OWRK/ReIKI8Zt7Hb19wTcQ0uTkGh13bQupcw7gPOvdo12/5CzNtNR7xLUtNtT3CGBQ6g3InjY720pvofUec22LJPr8PhEp2OMPyI40PdwWUdronCu9yQpdPx53bQlfLKnfOVhlnDYRBXve4Ov+IZTeMgdedm0NR+xoXJeQvdJ3CvziykSukwil16W/Oe7aGjIjqc/9ib4jQlJy0uArtN4A0+cvXFvDkmUJ47sJ1Y1ATLDNVXZkNPIepQzxy1ki9fqiwbUj/I+64zxWNzyZnPuhvohJ9K70VvXBixpcu2SAHU+Xd9EKdEJDNpYP3AQr3bQSpPQ6Y6/4dl1z7ZDbArsszjA7L0g7ibB0CDcidUWVoErvIMKZh2Xs0LUzcLW6V5NfiUgNEbaYmAVL6bXl0nJRc+1S72ua/D/cTjGPlQj7eUqd7A096rYlRjdPYlhz7VIvxpVG3cemDKF+WAwLY/6XelOZKTXXzsC4xvDjjtSN6kHLhLke6PrwM8h1raf40qjrGO7H9aTEbduucjS04ZrYU/4iuS5Z2Hdt0rvCLFdmLEXcU30AGddST62o+sLcf5l6k7CP+ru4pLYqX/VFyxbm/utQbx/r22ZEbTb2f5I2kns1Y1OQR8ZyofX+TjJxj1Rz7QQVnf1QzR26Oth0ueJVYcRP6ZUPac/Rx/5M6ixO1dhSrT3Y1DpiYmx3tF4ZUdpz9LD/dSg9PXES0LB71BwcGjKROuV28lnvnv7HHJsezheBGH5+X2CfSfRbMKW+5aGs3JFjMrjGibJc0S7TJzqjHrh2hDybj9XRXNZa89Aro55XBdbW5wti2c/5WJ7jJ1RolVUn/HWpb0I58Tziup6Rx7Dm2hnbRP1GM9PW/NFmQ4PtVRVN63Wvxfmu5sowDMMwDMMwDMMwDMMwDMMwDMMwzL+CpT//F/6beoV8zb2Jmt4Qryx6lTUCsENQ75HOkhXAO3EPVgyQtKtUy3C/e+FJg17Zjnew1Xrdb9InbG4WqfUAftG+WhLwPVyfg536+MU7m4C1CMk4ZznpXZzDYI1PDL2nS1hpvc5cNd7E2sJg05Fe7/7d3Fln8Cvc3bwB616auxsKl4WPghjemHrDqyDWeu1UNW5s2btPnSQ75oOdunEwWazfwgVG0kqluYCM9OIjWOGnfA2b9G4Ha63XKpvQ8perTvTifJNhi6+WMWmi7smEZf6G8MmhlyGq+NqP8GV84TLuJr7UIQVx+bDEoEpRZIz42gs40OuN4Mv8hXzelV7KX1isH+ewTWckikyVv+CfHuqVF7I16gN0VKypX6wPsE+zFPzkinolU9UH8OMGvSpnZqKsv13p/RsMun6X5x/y2LeAr8O66lsBwzBMP/wJfyGq8pgBk6IAAAAASUVORK5CYII=' + ); + + const msg = await client.sendMessage(remoteId, media, { + sendMediaAsSticker: true, + stickerAuthor: 'WWEBJS', + stickerName: 'My Sticker' + }); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.STICKER); + expect(msg.fromMe).to.equal(true); + expect(msg.hasMedia).to.equal(true); + expect(msg.to).to.equal(remoteId); + }); + + it('can send a location message', async function() { + const location = new Location(37.422, -122.084, 'Googleplex\nGoogle Headquarters'); + + const msg = await client.sendMessage(remoteId, location); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.LOCATION); + expect(msg.fromMe).to.equal(true); + expect(msg.to).to.equal(remoteId); + + expect(msg.location).to.be.instanceOf(Location); + expect(msg.location.latitude).to.equal(37.422); + expect(msg.location.longitude).to.equal(-122.084); + expect(msg.location.description).to.equal('Googleplex\nGoogle Headquarters'); + }); + + it('can send a vCard as a contact card message', async function() { + const vCard = `BEGIN:VCARD +VERSION:3.0 +FN;CHARSET=UTF-8:John Doe +N;CHARSET=UTF-8:Doe;John;;; +EMAIL;CHARSET=UTF-8;type=HOME,INTERNET:john@doe.com +TEL;TYPE=HOME,VOICE:1234567890 +REV:2021-06-06T02:35:53.559Z +END:VCARD`; + + const msg = await client.sendMessage(remoteId, vCard); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.CONTACT_CARD); + expect(msg.fromMe).to.equal(true); + expect(msg.to).to.equal(remoteId); + expect(msg.body).to.equal(vCard); + expect(msg.vCards).to.have.lengthOf(1); + expect(msg.vCards[0]).to.equal(vCard); + }); + + it('can optionally turn off vCard parsing', async function() { + const vCard = `BEGIN:VCARD +VERSION:3.0 +FN;CHARSET=UTF-8:John Doe +N;CHARSET=UTF-8:Doe;John;;; +EMAIL;CHARSET=UTF-8;type=HOME,INTERNET:john@doe.com +TEL;TYPE=HOME,VOICE:1234567890 +REV:2021-06-06T02:35:53.559Z +END:VCARD`; + + const msg = await client.sendMessage(remoteId, vCard, {parseVCards: false}); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.TEXT); // not a contact card + expect(msg.fromMe).to.equal(true); + expect(msg.to).to.equal(remoteId); + expect(msg.body).to.equal(vCard); + }); + + it('can send a Contact as a contact card message', async function() { + const contact = await client.getContactById(remoteId); + + const msg = await client.sendMessage(remoteId, contact); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.CONTACT_CARD); + expect(msg.fromMe).to.equal(true); + expect(msg.to).to.equal(remoteId); + expect(msg.body).to.match(/BEGIN:VCARD/); + expect(msg.vCards).to.have.lengthOf(1); + expect(msg.vCards[0]).to.match(/BEGIN:VCARD/); + }); + + it('can send multiple Contacts as a contact card message', async function () { + const contact1 = await client.getContactById(remoteId); + const contact2 = await client.getContactById('5511942167462@c.us'); //iFood + + const msg = await client.sendMessage(remoteId, [contact1, contact2]); + expect(msg).to.be.instanceOf(Message); + expect(msg.type).to.equal(MessageTypes.CONTACT_CARD_MULTI); + expect(msg.fromMe).to.equal(true); + expect(msg.to).to.equal(remoteId); + expect(msg.vCards).to.have.lengthOf(2); + expect(msg.vCards[0]).to.match(/BEGIN:VCARD/); + expect(msg.vCards[1]).to.match(/BEGIN:VCARD/); + }); + }); + + describe('Get Chats', function () { + it('can get a chat by its ID', async function () { + const chat = await client.getChatById(remoteId); + expect(chat).to.be.instanceOf(Chat); + expect(chat.id._serialized).to.eql(remoteId); + expect(chat.isGroup).to.eql(false); + }); + + it('can get all chats', async function () { + const chats = await client.getChats(); + expect(chats.length).to.be.greaterThanOrEqual(1); + + const chat = chats.find(c => c.id._serialized === remoteId); + expect(chat).to.exist; + expect(chat).to.be.instanceOf(Chat); + }); + }); + + describe('Get Contacts', function () { + it('can get a contact by its ID', async function () { + const contact = await client.getContactById(remoteId); + expect(contact).to.be.instanceOf(Contact); + expect(contact.id._serialized).to.eql(remoteId); + expect(contact.number).to.eql(remoteId.split('@')[0]); + }); + + it('can get all contacts', async function () { + const contacts = await client.getContacts(); + expect(contacts.length).to.be.greaterThanOrEqual(1); + + const contact = contacts.find(c => c.id._serialized === remoteId); + expect(contact).to.exist; + expect(contact).to.be.instanceOf(Contact); + }); + }); + + describe('Numbers and Users', function () { + it('can verify that a user is registered', async function () { + const isRegistered = await client.isRegisteredUser(remoteId); + expect(isRegistered).to.be.true; + }); + + it('can verify that a user is not registered', async function () { + const isRegistered = await client.isRegisteredUser('9999999999@c.us'); + expect(isRegistered).to.be.false; + }); + + it('can get a number\'s whatsapp id', async function () { + const number = remoteId.split('@')[0]; + const numberId = await client.getNumberId(number); + expect(numberId).to.eql({ + server: 'c.us', + user: number, + _serialized: `${number}@c.us` + }); + }); + + it('returns null when getting an unregistered number\'s whatsapp id', async function () { + const number = '9999999999'; + const numberId = await client.getNumberId(number); + expect(numberId).to.eql(null); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/helper.js b/tests/helper.js new file mode 100644 index 0000000000..febda54465 --- /dev/null +++ b/tests/helper.js @@ -0,0 +1,40 @@ +const path = require('path'); +const Client = require('../src/Client'); +const Util = require('../src/util/Util'); + +require('dotenv').config(); + +const remoteId = process.env.WWEBJS_TEST_REMOTE_ID; +if(!remoteId) throw new Error('The WWEBJS_TEST_REMOTE_ID environment variable has not been set.'); + +function getSessionFromEnv() { + const envSession = process.env.WWEBJS_TEST_SESSION; + if(envSession) return JSON.parse(envSession); + + const envSessionPath = process.env.WWEBJS_TEST_SESSION_PATH; + if(envSessionPath) { + const absPath = path.resolve(process.cwd(), envSessionPath); + return require(absPath); + } + + throw new Error('No session found in environment.'); +} + +function createClient({withSession, options: additionalOpts}={}) { + const options = {}; + if(withSession) { + options.session = getSessionFromEnv(); + } + + return new Client(Util.mergeDefault(options, additionalOpts || {})); +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +module.exports = { + sleep, + createClient, + remoteId +}; \ No newline at end of file diff --git a/tools/version-checker/.version b/tools/version-checker/.version new file mode 100644 index 0000000000..bb9b39443f --- /dev/null +++ b/tools/version-checker/.version @@ -0,0 +1 @@ +2.2126.10 diff --git a/tools/version-checker/update-version b/tools/version-checker/update-version new file mode 100755 index 0000000000..948b67dc5b --- /dev/null +++ b/tools/version-checker/update-version @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const puppeteer = require('puppeteer'); +const { DefaultOptions } = require('../../src/util/Constants'); + +const getLatestVersion = async () => { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + await page.setUserAgent(DefaultOptions.userAgent); + + await page.goto('https://web.whatsapp.com/', { waitUntil: 'load'}); + await page.waitForSelector('.landing-header'); + + const version = await page.evaluate(() => window.Debug.VERSION); + await browser.close(); + + return version; +}; + +const getCurrentVersion = () => { + try { + const versionFile = fs.readFileSync('./.version'); + return versionFile ? versionFile.toString() : null; + } catch(_) { + return null; + } +}; + +const updateVersion = async (oldVersion, newVersion) => { + const readmePath = '../../README.md'; + + const readme = fs.readFileSync(readmePath); + const newReadme = readme.toString().replaceAll(oldVersion, newVersion); + + fs.writeFileSync(readmePath, newReadme); + fs.writeFileSync('./.version', newVersion); + +}; + +(async () => { + const currentVersion = getCurrentVersion(); + const version = await getLatestVersion(); + + console.log(`Current version: ${currentVersion}`); + console.log(`Latest version: ${version}`); + + if(currentVersion !== version) { + console.log('Updating files...'); + await updateVersion(currentVersion, version); + console.log('Updated!'); + } else { + console.log('No changes.'); + } +})();