Skip to content

Commit

Permalink
feat: Multi-device support (#889)
Browse files Browse the repository at this point in the history
* 🚑 Added ready selector for multi-device

* SendMessage fix

* File management system and some fixes

* cleanup

* cleanup again

* eslint

* critical fix for reloading the same session

* Checking for valid folder name (regex)

* ESLint hotfix (regex escapes)

* Typings cleanup

* cleanup listener

* Multi-device Branch merge (#888)

* Duplicate

* qr fix and allow non-beta users to connect

* urgent: selector fix

* urgent: qr timeout fix

* fix

* Updated type so no TS error when sending list/buttons

* Update index.d.ts

* fix QueryExist for Multidevice (#928)

* creates isRegisteredUserBeta

* fix QueryExist

* fix Error: GROUP_JID: invalid jid type: Not an instance of WID issue (#926)

* fix Error: GROUP_JID: invalid jid type: Not an instance of WID issue

* clean code

* Cleanup

* Fix for update chrome error

* ESLint fix

* :red_light: fix for RMDIR

* Update README.md

* Update README.md

* fix: getProfilePicUrl fix by victormga (#941)

* fix: MD presence available/unavailable (#942)

* delete session when appropriate & fix for SW

* ignore QR timeout errors

* Presence and ChatState updates working for MD+Non-MD

* shell uses new session storage

* lint fix

* support session.json-based auth for non-md

* md fix

* md fix

* fix shell clientId

* remove exclusive mocha test

* make linkPreview default to false

* remove ignored errors on getQuotedMessage

* fix: dont modify existing this.options.puppeteer object

* tests work with new dir auth

* remove exclusive test

* fixes and tests for group creation and participant functions

* remove unused function

* wip fix group settings functions

* isRegisteredUser && getNumberId hotFix (#955)

* isRegisteredUser && getNumberId hotFix

A fix for client.isRegisteredUser and client.getNumberId. Use for reference or if you are stuck with MD and NEEDS this function. Problably Whatsapp will break this in a couple weeks

* fix for non-md

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>

* Fix WA 2.2146.9 MD +  victormga branch (#991)

* qrcode now uses observers instead of timeout

* automatic auth/qrcode detection

* Fix WA 2.2146.9 MD

Got from github:victormga/whatsapp-web.js#multidevice maybe it's behind pedro branch

Co-authored-by: victormga <victor_mga@hotmail.com>

* fix

* fix*

* getnumberid to multidevice (#1027)

* getNumberId to main

 isRegisteredUser && getNumberId hotFix #955 To main

* Update Client.js

Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com>

* Update Client.js

* Message.raw() (#1005)

* Message.raw()

* i just noticed

* Update index.d.ts

* Update index.d.ts

* Update Message.js

* Get rid of sharp now!!!!!!!! (#1045)

* commit 1

* finally, gotten rid of sharp

* pckg.json

* service worker fix & disableMessage option

* typings

* Update example.js

* clear session system

* Update Client.js

* Update Client.js

* Fix accepting group private invite (#1094)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* [MD] Add getCommonGroups with specific user. (#1097)

* Add getCommonGroups with specific user.

* Fix

* Fix

* Fix

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Fix getCommonGroups. (#1122)

* Fix of Unexpected identifier async destroy() (#1123)

* Fix of Unexpected identifier async destroy()

* Fix made in #1107

* Temporary fix for "Sticker" module

* some really quick changes

* Update Injected.js

* Update Injected.js

* Update index.d.ts

* fix: getNumberId Solved (#1142)

* getNumberId Solved

* isRegisteredUser Solved

* formmated

* Apply suggestions from code review

* Update src/util/Injected.js

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>

* Fix: "Chrome user data dir was not found ..."

fixes the error caused by puppeteer.

* Update Client.js (#1154)

* fix: getNumberId and isRegisteredUser (#1159)

* fix: getNumberId and isRegisteredUser

* Apply suggestions from code review

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>

* Update client.js

* Update Injected.js

* Update Client.js

* Update index.d.ts

* Update Client.js

* Update Client.js

* fix lint indentation

* fix auth_failure event for non-md, tests

* fix setting group subject

* fix finding Label module

* set remember-me after clearing localStorage

* fix: send messages to groups correctly on MD, use new ID format

* fix setting / getting contact status

* fix msg.getInfo, add message tests

* fix group settings functions

* fix set group description, handle errors in setSubject

* fix group invite functions

* fix leaving group

* bring back phone info for non-md users

* remove unused option, update typings

* add back jsdoc for qr event

* fix setting sticker metadata, clean up sticker functions

* rawData is a get only property

* fix and simplify getNumberId/isRegisteredUser

* fix getInviteInfo

* setDisplayName returns bool, not yet implemented for md

* fix: stream module (#1241)

* linkPreview has no effect on MD, return default to true

* fix: del linkPreview option on md

* cleanup, types and docs updates

* update readmes / test notes

* remove DS_Store

* DS_Store in gitignore

* test stability (timeouts/sleeps)

Co-authored-by: Rajeh Taher <rajeh@reforward.tk>
Co-authored-by: Gustavo B <52040719+Gugabit@users.noreply.github.com>
Co-authored-by: Maikel Ortega Hernández <maikeloh@gmail.com>
Co-authored-by: victormga <victor_mga@hotmail.com>
Co-authored-by: Pedro Lopez <pedroslopez@me.com>
Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com>
Co-authored-by: gon <68490103+nekiak@users.noreply.github.com>
Co-authored-by: Alon Schwartzblat <63599777+Schwartzblat@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Šebestíček <44745014+SebestikCZ@users.noreply.github.com>
Co-authored-by: Emmanuel Anaya Luna <38712443+KeruMx@users.noreply.github.com>
Co-authored-by: L337C0D3R <51872799+L337C0D3R@users.noreply.github.com>
Co-authored-by: Reni Delonzek <renidelonzek@gmail.com>
  • Loading branch information
14 people committed Feb 27, 2022
1 parent 8ef37b6 commit 0d55d40
Show file tree
Hide file tree
Showing 20 changed files with 1,132 additions and 553 deletions.
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
WWEBJS_TEST_SESSION_PATH=test_session.json
WWEBJS_TEST_REMOTE_ID=XXXXXXXXXX@c.us
WWEBJS_TEST_REMOTE_ID=XXXXXXXXXX@c.us
WWEBJS_TEST_CLIENT_ID=authenticated
WWEBJS_TEST_MD=1
9 changes: 7 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ typings/
# next.js build output
.next

# macOS Thumbnails
# macOS
._*
.DS_Store

# Test sessions
*session.json
*session.json

# user data
WWebJS/
userDataDir/
35 changes: 8 additions & 27 deletions example.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
const fs = require('fs');
const { Client, Location, List, Buttons } = require('./index');

const SESSION_FILE_PATH = './session.json';
let sessionCfg;
if (fs.existsSync(SESSION_FILE_PATH)) {
sessionCfg = require(SESSION_FILE_PATH);
}

const client = new Client({ puppeteer: { headless: false }, session: sessionCfg });
// You can use an existing session and avoid scanning a QR code by adding a "session" object to the client options.
// This object must include WABrowserId, WASecretBundle, WAToken1 and WAToken2.
const client = new Client({
clientId: 'example',
puppeteer: { headless: false }
});

// You also could connect to an existing instance of a browser
// {
Expand All @@ -25,18 +19,12 @@ client.on('qr', (qr) => {
console.log('QR RECEIVED', qr);
});

client.on('authenticated', (session) => {
console.log('AUTHENTICATED', session);
sessionCfg=session;
fs.writeFile(SESSION_FILE_PATH, JSON.stringify(session), function (err) {
if (err) {
console.error(err);
}
});
client.on('authenticated', () => {
console.log('AUTHENTICATED');
});

client.on('auth_failure', msg => {
// Fired if session restore was unsuccessfull
// Fired if session restore was unsuccessful
console.error('AUTHENTICATION FAILURE', msg);
});

Expand Down Expand Up @@ -124,9 +112,8 @@ client.on('message', async msg => {
client.sendMessage(msg.from, `
*Connection info*
User name: ${info.pushname}
My number: ${info.me.user}
My number: ${info.wid.user}
Platform: ${info.platform}
WhatsApp version: ${info.phone.wa_version}
`);
} else if (msg.body === '!mediainfo' && msg.hasMedia) {
const attachmentData = await msg.downloadMedia();
Expand Down Expand Up @@ -267,12 +254,6 @@ client.on('group_update', (notification) => {
console.log('update', notification);
});

client.on('change_battery', (batteryInfo) => {
// Battery percentage for attached device has changed
const { battery, plugged } = batteryInfo;
console.log(`Battery: ${battery}% - Charging? ${plugged}`);
});

client.on('change_state', state => {
console.log('CHANGE STATE', state );
});
Expand Down
77 changes: 60 additions & 17 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ declare namespace WAWebJS {
/** Returns the contact ID's profile picture URL, if privacy settings allow it */
getProfilePicUrl(contactId: string): Promise<string>

/** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */
getCommonGroups(contactId: string): Promise<ChatId[]>

/** Gets the current connection state for the client */
getState(): Promise<WAState>

Expand Down Expand Up @@ -118,6 +121,9 @@ declare namespace WAWebJS {
/** Marks the client as online */
sendPresenceAvailable(): Promise<void>

/** Marks the client as offline */
sendPresenceUnavailable(): Promise<void>

/** Mark as seen for the Chat */
sendSeen(chatId: string): Promise<boolean>

Expand All @@ -134,7 +140,7 @@ declare namespace WAWebJS {
* Sets the current user's display name
* @param displayName New display name
*/
setDisplayName(displayName: string): Promise<void>
setDisplayName(displayName: string): Promise<boolean>

/** Changes and returns the archive state of the Chat */
unarchiveChat(chatId: string): Promise<boolean>
Expand All @@ -150,11 +156,17 @@ declare namespace WAWebJS {

/** Emitted when authentication is successful */
on(event: 'authenticated', listener: (
/** Object containing session information. Can be used to restore the session */
session: ClientSession
/**
* Object containing session information. Can be used to restore the session
* @deprecated
*/
session?: ClientSession
) => void): this

/** Emitted when the battery percentage for the attached device changes */
/**
* Emitted when the battery percentage for the attached device changes
* @deprecated
*/
on(event: 'change_battery', listener: (batteryInfo: BatteryInfo) => void): this

/** Emitted when the connection state changes */
Expand Down Expand Up @@ -249,14 +261,12 @@ declare namespace WAWebJS {

/** Current connection information */
export interface ClientInfo {
/**
* Current user ID
* @deprecated Use .wid instead
*/
me: ContactId
/** Current user ID */
wid: ContactId
/** Information about the phone this client is connected to */
/**
* Information about the phone this client is connected to. Not available in multi-device.
* @deprecated
*/
phone: ClientInfoPhone
/** Platform the phone is running on */
platform: string
Expand All @@ -267,7 +277,10 @@ declare namespace WAWebJS {
getBatteryStatus: () => Promise<BatteryInfo>
}

/** Information about the phone this client is connected to */
/**
* Information about the phone this client is connected to
* @deprecated
*/
export interface ClientInfoPhone {
/** WhatsApp Version running on the phone */
wa_version: string
Expand Down Expand Up @@ -300,8 +313,19 @@ declare namespace WAWebJS {
/** Restart client with a new session (i.e. use null 'session' var) if authentication fails
* @default false */
restartOnAuthFail?: boolean
/** Whatsapp session to restore. If not set, will start a new session */
/**
* Enable authentication via a `session` option.
* @deprecated Will be removed in a future release
*/
useDeprecatedSessionAuth?: boolean
/**
* WhatsApp session to restore. If not set, will start a new session
* @deprecated Set `useDeprecatedSessionAuth: true` to enable. This auth method is not supported by MultiDevice and will be removed in a future release.
*/
session?: ClientSession
/** Client id to distinguish instances if you are using multiple, otherwise keep empty if you are using only one instance
* @default '' */
clientId: string
/** If another whatsapp web session is detected (another browser), take over the session in the current browser
* @default false */
takeoverOnConflict?: boolean,
Expand All @@ -314,16 +338,25 @@ declare namespace WAWebJS {
/** Ffmpeg path to use when formating videos to webp while sending stickers
* @default 'ffmpeg' */
ffmpegPath?: string
/** Path to place session objects in
@default './WWebJS' */
dataPath?: string
}

/** Represents a Whatsapp client session */
/**
* Represents a WhatsApp client session
* @deprecated
*/
export interface ClientSession {
WABrowserId: string,
WASecretBundle: string,
WAToken1: string,
WAToken2: string,
}

/**
* @deprecated
*/
export interface BatteryInfo {
/** The current battery percentage */
battery: number,
Expand Down Expand Up @@ -608,6 +641,13 @@ declare namespace WAWebJS {
selectedButtonId?: string,
/** Selected list row ID */
selectedRowId?: string,
/** Returns message in a raw format */
rawData: object,
/*
* Reloads this Message object's data in-place with the latest values from WhatsApp Web.
* Note that the Message must still be in the web app cache for this to work, otherwise will return null.
*/
reload: () => Promise<Message>,
/** Accept the Group V4 Invite in message */
acceptGroupV4Invite: () => Promise<{status: number}>,
/** Deletes the message from the chat */
Expand Down Expand Up @@ -679,7 +719,7 @@ declare namespace WAWebJS {

/** Options for sending a message */
export interface MessageSendOptions {
/** Show links preview */
/** Show links preview. Has no effect on multi-device accounts. */
linkPreview?: boolean
/** Send audio as voice message */
sendAudioAsVoice?: boolean
Expand Down Expand Up @@ -741,7 +781,7 @@ declare namespace WAWebJS {
static fromUrl: (url: string, options?: MediaFromURLOptions) => Promise<MessageMedia>
}

export type MessageContent = string | MessageMedia | Location | Contact | Contact[] | List | Buttons
export type MessageContent = string | MessageMedia | Location | Contact | Contact[] | List | Buttons

/**
* Represents a Contact on WhatsApp
Expand Down Expand Up @@ -834,6 +874,9 @@ declare namespace WAWebJS {

/** Gets the Contact's current "about" info. Returns null if you don't have permission to read their status. */
getAbout: () => Promise<string | null>,

/** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */
getCommonGroups: () => Promise<ChatId[]>

}

Expand Down Expand Up @@ -1011,9 +1054,9 @@ declare namespace WAWebJS {
/** Demotes participants by IDs to regular users */
demoteParticipants: ChangeParticipantsPermisions;
/** Updates the group subject */
setSubject: (subject: string) => Promise<void>;
setSubject: (subject: string) => Promise<boolean>;
/** Updates the group description */
setDescription: (description: string) => Promise<void>;
setDescription: (description: string) => Promise<boolean>;
/** Updates the group settings to only allow admins to send messages
* @param {boolean} [adminsOnly=true] Enable or disable this option
* @returns {Promise<boolean>} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "./index.js",
"typings": "./index.d.ts",
"scripts": {
"test": "mocha tests --recursive",
"test": "mocha tests --recursive --timeout 5000",
"test-single": "mocha",
"shell": "node --experimental-repl-await ./shell.js",
"generate-docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose"
Expand Down Expand Up @@ -35,12 +35,12 @@
"mime": "^3.0.0",
"node-fetch": "^2.6.5",
"node-webpmux": "^3.1.0",
"puppeteer": "^13.0.0",
"sharp": "^0.28.3"
"puppeteer": "^13.0.0"
},
"devDependencies": {
"@types/node-fetch": "^2.5.12",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"dotenv": "^16.0.0",
"eslint": "^8.4.1",
"eslint-plugin-mocha": "^10.0.3",
Expand Down
13 changes: 5 additions & 8 deletions shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,12 @@
*/

const repl = require('repl');
const fs = require('fs');

const { Client } = require('./index');

const SESSION_FILE_PATH = './session.json';
let sessionCfg;
if (fs.existsSync(SESSION_FILE_PATH)) {
sessionCfg = require(SESSION_FILE_PATH);
}

const client = new Client({
puppeteer: { headless: false },
session: sessionCfg
clientId: 'shell'
});

console.log('Initializing...');
Expand All @@ -30,6 +23,10 @@ client.on('qr', () => {
console.log('Please scan the QR code on the browser.');
});

client.on('authenticated', (session) => {
console.log(JSON.stringify(session));
});

client.on('ready', () => {
const shell = repl.start('wwebjs> ');
shell.context.client = client;
Expand Down

0 comments on commit 0d55d40

Please sign in to comment.