diff --git a/.env.example b/.env.example
deleted file mode 100644
index 3add97c..0000000
--- a/.env.example
+++ /dev/null
@@ -1,3 +0,0 @@
-WWEBJS_TEST_REMOTE_ID=XXXXXXXXXX@c.us
-WWEBJS_TEST_CLIENT_ID=authenticated
-WWEBJS_TEST_MD=1
\ No newline at end of file
diff --git a/README.md b/README.md
index d97f389..bacbd17 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,39 @@
[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#table-of-contents)
# MYWAJS
-[COMMUNITY - CLICK HERE](https://chat.whatsapp.com/BIHE3USWr4lAnahwJTQEOX) - [DOCS MYWAJS - CLICK HERE](https://amiruldev20.github.io/mywajs)
+[COMMUNITY - CLICK HERE](https://chat.whatsapp.com/BIHE3USWr4lAnahwJTQEOX) - [DOCS MYWAJS - CLICK HERE](https://mywajs.amirull.dev)
- > *NB*: this is whatsapp-web.js fork with features added from wajs wpp. permits had been granted by WPP
-
-
+ > *NB*: this is whatsapp-web.js fork with features from wajs wpp. Previously, permits had been granted by WPP
+
+
+
[![npm version](https://img.shields.io/npm/v/mywajs.svg?color=green)](https://www.npmjs.com/package/mywajs)
![node](https://img.shields.io/node/v/mywajs)
[![Downloads](https://img.shields.io/npm/dm/mywajs.svg)](https://www.npmjs.com/package/mywajs)
-[![GitHub contributors](https://img.shields.io/github/contributors/amiruldev20/mywajs.svg)](https://github.com/your-username/your-repo/graphs/contributors)
+[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/amiruldev20/mywajs.svg)](http://isitmaintained.com/project/amiruldev20/mywajs "Average time to resolve an issue")
+[![Percentage of issues still open](http://isitmaintained.com/badge/open/amiruldev20/mywajs.svg)](http://isitmaintained.com/project/amiruldev20/mywajs "Percentage of issues still open")
+
```
-This project is a wwebjs fork with more features imported from WPP, developed using playwright and wajs.
-This is an unofficial whatsapp library.
-You can get banned for using this library.
-Im not responsible if you got banned.
-If you need example you can find in on the examples folder
+it's the wwebjs library redeveloped using
+playwright and wajs.
+because in my opinion playwright is lighter.
+This is an unofficial library,
+doesn't mean it's anti-banned.
+use accordingly, don't overspend
-Why am I using playwright?
-In my opinion playwright is faster than puppeteer
+for example check folder example
+docs? check folder docs
```
-> [!NOTE]
-> If you want to add/fix a feature in this project, feel free to edit this project code and make a new PR
-
## Supported features
| Feature | Status |
| ------------- | ------------- |
| Login via phone | ✅ |
| Multi Device | ✅ |
+| Creae Channel | ✅ |
| Join Wa Beta | ✅ |
| Change theme | ✅ |
| Send polling | ✅ |
diff --git a/docs/start.md b/docs/start.md
deleted file mode 100644
index 124c1c3..0000000
--- a/docs/start.md
+++ /dev/null
@@ -1,24 +0,0 @@
-> [!NOTE]
-> it's a re-development library of wwebjs tweaked using playwright and wajs. This library is made easier and lighter, if there are additional request please open an issue or discuss on the repo
-
-don't forget to join the whatsapp community to get the latest information from this lib [Join Community](https://chat.whatsapp.com/BIHE3USWr4lAnahwJTQEOX)
-
-> [!WARNING]
-> MywaJS
created using the esm module type (ECMAScript Modules). please add type: 'module' to your package.json. and use import module using import, not require
-
-### Installing MywaJS
-You can get the module from npm:
-
-```bash
-npm i mywajs
-```
-
-> [!IMPORTANT]
-> can be run on:
-> - vps
-> - rdp
-> - hosting (gold server)
-> - replit
-
-### Playwright Installation
-after installing nodejs, please install playwr with the npx playwright install-deps
command
diff --git a/docs/tes.html b/docs/tes.html
deleted file mode 100644
index 6d2a233..0000000
--- a/docs/tes.html
+++ /dev/null
@@ -1 +0,0 @@
-testing
diff --git a/examples/linkingdevice.js b/example/linkingdevice.js
similarity index 100%
rename from examples/linkingdevice.js
rename to example/linkingdevice.js
diff --git a/examples/linkingphone.js b/example/linkingphone.js
similarity index 100%
rename from examples/linkingphone.js
rename to example/linkingphone.js
diff --git a/index.d.ts b/index.d.ts
index 005bdd6..baaf809 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -7,1717 +7,1752 @@
* ig: amirul.dev
*/
import {
- EventEmitter
+ EventEmitter
} from "events";
import {
- AxiosInterceptorOptions
+ AxiosInterceptorOptions
} from "axios";
import * as playwright from "playwright-chromium";
import PollVote from "./src/structures/PollVote.js";
declare namespace WAWebJS {
- export class Client extends EventEmitter {
- constructor(options: ClientOptions);
+ export class Client extends EventEmitter {
+ constructor(options: ClientOptions);
- /** Current connection information */
- public info: ClientInfo;
+ /** Current connection information */
+ public info: ClientInfo;
- /** playwright page running WhatsApp Web */
- mPage ? : playwright.Page;
+ /** playwright page running WhatsApp Web */
+ mPage?: playwright.Page;
- /** playwright browser running WhatsApp Web */
- pupBrowser ? : playwright.Browser;
+ /** playwright browser running WhatsApp Web */
+ pupBrowser?: playwright.Browser;
- /**Accepts an invitation to join a group */
- acceptInvite(inviteCode: string): Promise < string > ;
+ /**Accepts an invitation to join a group */
+ acceptInvite(inviteCode: string): Promise;
- /**Returns an object with information about the invite code's group */
- getInviteInfo(inviteCode: string): Promise < object > ;
+ /**Returns an object with information about the invite code's group */
+ getInviteInfo(inviteCode: string): Promise;
- /** Enables and returns the archive state of the Chat */
- archiveChat(chatId: string): Promise < boolean > ;
+ /** Enables and returns the archive state of the Chat */
+ archiveChat(chatId: string): Promise;
- /** Pins the Chat and returns its new Pin state */
- pinChat(chatId: string): Promise < boolean > ;
+ /** Pins the Chat and returns its new Pin state */
+ pinChat(chatId: string): Promise;
- /** Unpins the Chat and returns its new Pin state */
- unpinChat(chatId: string): Promise < boolean > ;
+ /** Unpins the Chat and returns its new Pin state */
+ unpinChat(chatId: string): Promise;
- /**
- * Create a new group
- * @param name group title
- * @param participants an array of Contacts or contact IDs to add to the group
- */
- createGroup(
- name: string,
- participants: Contact[] | string[]
- ): Promise < CreateGroupResult > ;
+ /**
+ * Create a new group
+ * @param name group title
+ * @param participants an array of Contacts or contact IDs to add to the group
+ */
+ createGroup(
+ name: string,
+ participants: Contact[] | string[]
+ ): Promise;
- /** Closes the client */
- destroy(): Promise < void > ;
+ /** Closes the client */
+ destroy(): Promise;
- /** Logs out the client, closing the current session */
- logout(): Promise < void > ;
+ /** Logs out the client, closing the current session */
+ logout(): Promise;
- /** Get all blocked contacts by host account */
- getBlockedContacts(): Promise < Contact[] > ;
+ /** Get all blocked contacts by host account */
+ getBlockedContacts(): Promise;
- /** Get chat instance by ID */
- getChatById(chatId: string): Promise < Chat > ;
+ /** Get chat instance by ID */
+ getChatById(chatId: string): Promise;
- /** Get all current chat instances */
- getChats(): Promise < Chat[] > ;
+ /** Get all current chat instances */
+ getChats(): Promise;
- /** Get contact instance by ID */
- getContactById(contactId: string): Promise < Contact > ;
+ /** Get contact instance by ID */
+ getContactById(contactId: string): Promise;
- /** Get all current contact instances */
- getContacts(): Promise < Contact[] > ;
+ /** Get all current contact instances */
+ getContacts(): Promise;
- /** Get the country code of a WhatsApp ID. (154185968@c.us) => (1) */
- getCountryCode(number: string): Promise < string > ;
+ /** Get the country code of a WhatsApp ID. (154185968@c.us) => (1) */
+ getCountryCode(number: string): Promise;
- /** Get the formatted number of a WhatsApp ID. (12345678901@c.us) => (+1 (234) 5678-901) */
- getFormattedNumber(number: string): Promise < string > ;
+ /** Get the formatted number of a WhatsApp ID. (12345678901@c.us) => (+1 (234) 5678-901) */
+ getFormattedNumber(number: string): Promise;
- /** Get all current Labels */
- getLabels(): Promise < Label[] > ;
+ /** Get all current Labels */
+ getLabels(): Promise;
- /** Get Label instance by ID */
- getLabelById(labelId: string): Promise < Label > ;
+ /** Get Label instance by ID */
+ getLabelById(labelId: string): Promise;
- /** Get all Labels assigned to a Chat */
- getChatLabels(chatId: string): Promise < Label[] > ;
+ /** Get all Labels assigned to a Chat */
+ getChatLabels(chatId: string): Promise;
- /** Get all Chats for a specific Label */
- getChatsByLabelId(labelId: string): Promise < Chat[] > ;
+ /** Get all Chats for a specific Label */
+ getChatsByLabelId(labelId: string): Promise;
- /** Returns the contact ID's profile picture URL, if privacy settings allow it */
- getProfilePicUrl(contactId: string): Promise < string > ;
+ /** Returns the contact ID's profile picture URL, if privacy settings allow it */
+ getProfilePicUrl(contactId: string): Promise;
- /** 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 Contact's common groups with you. Returns empty array if you don't have any common group. */
+ getCommonGroups(contactId: string): Promise;
- /** Gets the current connection state for the client */
- getState(): Promise < WAState > ;
+ /** Gets the current connection state for the client */
+ getState(): Promise;
- /** Returns the version of WhatsApp Web currently being run */
- getWWebVersion(): Promise < string > ;
+ /** Returns the version of WhatsApp Web currently being run */
+ getWWebVersion(): Promise;
- /** Sets up events and requirements, kicks off authentication request */
- initialize(): Promise < void > ;
+ /** Sets up events and requirements, kicks off authentication request */
+ initialize(): Promise;
- /** Check if a given ID is registered in whatsapp */
- isRegisteredUser(contactId: string): Promise < boolean > ;
+ /** Check if a given ID is registered in whatsapp */
+ isRegisteredUser(contactId: string): Promise;
- /** Get the registered WhatsApp ID for a number. Returns null if the number is not registered on WhatsApp. */
- getNumberId(number: string): Promise < ContactId | null > ;
+ /** Get the registered WhatsApp ID for a number. Returns null if the number is not registered on WhatsApp. */
+ getNumberId(number: string): Promise;
- /**
- * @param chatId ID of the chat that will be muted
- * @param unmuteDate number, in the form of seconds for the duration of the muted chat
- */
- muteChat(chatId: string, unmuteDate ? : number): Promise < void > ;
+ /**
+ * @param chatId ID of the chat that will be muted
+ * @param unmuteDate number, in the form of seconds for the duration of the muted chat
+ */
+ muteChat(chatId: string, unmuteDate?: number): Promise;
- /**
- * @param chatId ID of the chat that will be muted
- * @param ephemeralDuration number, in the form of seconds for the duration of the temporary message in chat
- */
- setEphemeral(chatId: string, ephemeralDuration ? : number): Promise < void > ;
+ /**
+ * @param chatId ID of the chat that will be muted
+ * @param ephemeralDuration number, in the form of seconds for the duration of the temporary message in chat
+ */
+ setEphemeral(chatId: string, ephemeralDuration?: number): Promise;
- /** Force reset of connection state for the client */
- resetState(): Promise < void > ;
+ /** Force reset of connection state for the client */
+ resetState(): Promise;
- /** Send a message to a specific chatId */
- sendMessage(
- chatId: string,
- content: MessageContent,
- options ? : MessageSendOptions
- ): Promise < Message > ;
+ /** Send a message to a specific chatId */
+ sendMessage(
+ chatId: string,
+ content: MessageContent,
+ options?: MessageSendOptions
+ ): Promise;
- /** Searches for messages */
- searchMessages(
- query: string,
- options ? : {
- chatId ? : string;page ? : number;limit ? : number
- }
- ): Promise < Message[] > ;
+ /** Searches for messages */
+ searchMessages(
+ query: string,
+ options?: {
+ chatId?: string; page?: number; limit?: number
+ }
+ ): Promise;
- /** Marks the client as online */
- sendPresenceAvailable(): Promise < void > ;
+ /** Marks the client as online */
+ sendPresenceAvailable(): Promise;
- /** Marks the client as offline */
- sendPresenceUnavailable(): Promise < void > ;
+ /** Marks the client as offline */
+ sendPresenceUnavailable(): Promise;
- /** Mark as seen for the Chat */
- sendSeen(chatId: string): Promise < boolean > ;
+ /** Mark as seen for the Chat */
+ sendSeen(chatId: string): Promise;
- /** Mark the Chat as unread */
- markChatUnread(chatId: string): Promise < void > ;
+ /** Mark the Chat as unread */
+ markChatUnread(chatId: string): Promise;
- /**
- * Sets the current user's status message
- * @param status New status message
- */
- setStatus(status: string): Promise < void > ;
+ /**
+ * Sets the current user's status message
+ * @param status New status message
+ */
+ setStatus(status: string): Promise;
- /**
- * Sets the current user's display name
- * @param displayName New display name
- */
- setDisplayName(displayName: string): Promise < boolean > ;
+ /**
+ * Sets the current user's display name
+ * @param displayName New display name
+ */
+ setDisplayName(displayName: string): Promise;
- /** Changes and returns the archive state of the Chat */
- unarchiveChat(chatId: string): Promise < boolean > ;
+ /** Changes and returns the archive state of the Chat */
+ unarchiveChat(chatId: string): Promise;
- /** Unmutes the Chat */
- unmuteChat(chatId: string): Promise < void > ;
-
- /** Sets the current user's profile picture */
- setProfilePicture(media: MessageMedia): Promise < boolean > ;
-
- /** Deletes the current user's profile picture */
- deleteProfilePicture(): Promise < boolean > ;
-
- /** Send a call to someone */
- sendCall(chatId: string, options: CallOptions): Promise < Boolean > ;
-
- /** Hang up the call in progress */
- endCall(chatId: string): Promise < Boolean > ;
-
- /** Receive a phone call from someone */
- acceptCall(chatId: string): Promise < Boolean > ;
-
- /** Displays last seen status (Conditions : No Privacy) */
- getLastSeen(chatId: string): Promise < string | Boolean | Number > ;
-
- /** Archives or No all selected conversation, 'chat' or 'group' except pinned ones */
- archiveAll(type: string, status: boolean): Promise < Boolean > ;
-
- /** Mute or No all selected conversation, 'chat' or 'group' except pinned ones */
- muteAll(type: string, status: boolean): Promise < Boolean > ;
-
- /** Displays connection status */
- getHost(): Promise < Object > ;
-
- /** Change the display theme 'light' or 'dark' */
- setTheme(type: string): Promise < void > ;
-
- /** get display theme */
- getTheme(): Promise < string > ;
-
- /** Delete all messages */
- clearMessage(chatId: string): Promise < Boolean > ;
-
- /** fetch stories user */
- getStories(chatId: string): Promise < Array > ;
-
- /** get contact by name */
- getContactByName(name: string): Promise < void > ;
-
- /** send polling message */
- sendPoll(
- chatId: string,
- name: string,
- choices: Array,
- options: MessageSendOptions
- ): Promise < Message > ;
-
- /** read status user */
- sendReadStatus(chatId: string, msgId: string): Promise < void > ;
-
- /** Generic event */
- on(event: string, listener: (...args: any) => void): this;
-
- /** Emitted when there has been an error while trying to restore an existing session */
- on(event: "auth_failure", listener: (message: string) => void): this;
-
- /** Emitted when authentication is successful */
- on(
- event: "authenticated",
- listener: (
- /**
- * Object containing session information, when using LegacySessionAuth. Can be used to restore the session
- */
- session ? : ClientSession
- ) => void
- ): this;
-
- /**
- * 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 */
- on(
- event: "change_state",
- listener: (
- /** the new connection state */
- state: WAState
- ) => void
- ): this;
-
- /** Emitted when the client has been disconnected */
- on(
- event: "disconnected",
- listener: (
- /** reason that caused the disconnect */
- reason: WAState | "NAVIGATION"
- ) => void
- ): this;
-
- /** Emitted when a user joins the chat via invite link or is added by an admin */
- on(
- event: "group_join",
- listener: (
- /** GroupNotification with more information about the action */
- notification: GroupNotification
- ) => void
- ): this;
-
- /** Emitted when a user leaves the chat or is removed by an admin */
- on(
- event: "group_leave",
- listener: (
- /** GroupNotification with more information about the action */
- notification: GroupNotification
- ) => void
- ): this;
-
- /** Emitted when a current user is promoted to an admin or demoted to a regular user */
- on(
- event: "group_admin_changed",
- listener: (
- /** GroupNotification with more information about the action */
- notification: GroupNotification
- ) => void
- ): this;
-
- /** Emitted when group settings are updated, such as subject, description or picture */
- on(
- event: "group_update",
- listener: (
- /** GroupNotification with more information about the action */
- notification: GroupNotification
- ) => void
- ): this;
-
- /** Emitted when a contact or a group participant changed their phone number. */
- on(
- event: "contact_changed",
- listener: (
- /** Message with more information about the event. */
- message: Message,
- /** Old user's id. */
- oldId: String,
- /** New user's id. */
- newId: String,
- /** Indicates if a contact or a group participant changed their phone number. */
- isContact: Boolean
- ) => void
- ): this;
-
- /** Emitted when media has been uploaded for a message sent by the client */
- on(
- event: "media_uploaded",
- listener: (
- /** The message with media that was uploaded */
- message: Message
- ) => void
- ): this;
-
- /** Emitted when a new message is received */
- on(
- event: "message",
- listener: (
- /** The message that was received */
- message: Message
- ) => void
- ): this;
-
- /** Emitted when an ack event occurrs on message type */
- on(
- event: "message_ack",
- listener: (
- /** The message that was affected */
- message: Message,
- /** The new ACK value */
- ack: MessageAck
- ) => void
- ): this;
-
- /** Emitted when a chat unread count changes */
- on(
- event: "unread_count",
- listener: (
- /** The chat that was affected */
- chat: Chat
- ) => void
- ): this;
-
- /** Emitted when a new message is created, which may include the current user's own messages */
- on(
- event: "message_create",
- listener: (
- /** The message that was created */
- message: Message
- ) => void
- ): this;
-
- /** Emitted when a message is deleted for everyone in the chat */
- on(
- event: "message_revoke_everyone",
- listener: (
- /** The message that was revoked, in its current state. It will not contain the original message's data */
- message: Message,
- /**The message that was revoked, before it was revoked.
- * It will contain the message's original data.
- * Note that due to the way this data is captured,
- * it may be possible that this param will be undefined. */
- revoked_msg ? : Message | null
- ) => void
- ): this;
-
- /** Emitted when a message is deleted by the current user */
- on(
- event: "message_revoke_me",
- listener: (
- /** The message that was revoked */
- message: Message
- ) => void
- ): this;
-
- /** Emitted when a reaction is sent, received, updated or removed */
- on(
- event: "message_reaction",
- listener: (
- /** The reaction object */
- reaction: Reaction
- ) => void
- ): this;
-
- /** Emitted when a chat is removed */
- on(
- event: "chat_removed",
- listener: (
- /** The chat that was removed */
- chat: Chat
- ) => void
- ): this;
-
- /** Emitted when a chat is archived/unarchived */
- on(
- event: "chat_archived",
- listener: (
- /** The chat that was archived/unarchived */
- chat: Chat,
- /** State the chat is currently in */
- currState: boolean,
- /** State the chat was previously in */
- prevState: boolean
- ) => void
- ): this;
-
- /** Emitted when loading screen is appearing */
- on(
- event: "loading_screen",
- listener: (percent: string, message: string) => void
- ): this;
-
- /** Emitted when the QR code is received */
- on(
- event: "qr",
- listener: (
- /** qr code string
- * @example ```1@9Q8tWf6bnezr8uVGwVCluyRuBOJ3tIglimzI5dHB0vQW2m4DQ0GMlCGf,f1/vGcW4Z3vBa1eDNl3tOjWqLL5DpYTI84DMVkYnQE8=,ZL7YnK2qdPN8vKo2ESxhOQ==``` */
- qr: string
- ) => void
- ): this;
-
- /* login with code */
- on(event: "code", listener: (code: string) => void): this;
-
- /** Emitted when a call is received */
- on(
- event: "call",
- listener: (
- /** The call that started */
- call: Call
- ) => void
- ): this;
-
- /** Emitted when the client has initialized and is ready to receive messages */
- on(event: "ready", listener: () => void): this;
-
- /** Emitted when the RemoteAuth session is saved successfully on the external Database */
- on(event: "remote_session_saved", listener: () => void): this;
-
- /** Emitted when a poll vote is received */
- on(
- event: "poll_vote",
- listener: (
- /** The poll vote */
- vote: PollVote
- ) => void
- ): this;
- }
-
- /** 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. Not available in multi-device.
- * @deprecated
- */
- phone: ClientInfoPhone;
- /** Platform the phone is running on */
- platform: string;
- /** Name configured to be shown in push notifications */
- pushname: string;
-
- /** Get current battery percentage and charging status for the attached device */
- getBatteryStatus: () => Promise < BatteryInfo > ;
- }
-
- /**
- * Information about the phone this client is connected to
- * @deprecated
- */
- export interface ClientInfoPhone {
- /** WhatsApp Version running on the phone */
- wa_version: string;
- /** OS Version running on the phone (iOS or Android version) */
- os_version: string;
- /** Device manufacturer */
- device_manufacturer: string;
- /** Device model */
- device_model: string;
- /** OS build number */
- os_build_number: string;
- }
-
- /** Options for initializing the whatsapp client */
- export interface ClientOptions {
- /** Timeout for authentication selector in playwright
- * @default 0 */
- authTimeoutMs ? : number;
- /** playwright launch options. View docs here: https://github.com/microsoft/playwright/ */
- playwright ? : playwright.LaunchOptions & playwright.ConnectOptions;
- /** Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. */
- authStrategy ? : AuthStrategy;
- /** How many times should the qrcode be refreshed before giving up
- * @default 0 (disabled) */
- qrMaxRetries ? : number;
- /**
- * @deprecated This option should be set directly on the LegacySessionAuth
- */
-
- /* linking method */
- linkingMethod: LinkingMethod;
-
- restartOnAuthFail ? : boolean;
- /**
- * @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
- */
- session ? : ClientSession;
- /** If another whatsapp web session is detected (another browser), take over the session in the current browser
- * @default false */
- takeoverOnConflict ? : boolean;
- /** How much time to wait before taking over the session
- * @default 0 */
- takeoverTimeoutMs ? : number;
- /** User agent to use in playwright.
- * @default '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' */
- userAgent ? : string;
- /** Ffmpeg path to use when formating videos to webp while sending stickers
- * @default 'ffmpeg' */
- ffmpegPath ? : string;
- /** Object with proxy autentication requirements @default: undefined */
- proxyAuthentication ? : {
- username: string;password: string
- } | undefined;
- }
-
- /**
- * Base class which all authentication strategies extend
- */
- export abstract class AuthStrategy {
- setup: (client: Client) => void;
- beforeBrowserInitialized: () => Promise < void > ;
- afterBrowserInitialized: () => Promise < void > ;
- onAuthenticationNeeded: () => Promise < {
- failed ? : boolean;
- restart ? : boolean;
- failureEventPayload ? : any;
- } > ;
- getAuthEventPayload: () => Promise < any > ;
- afterAuthReady: () => Promise < void > ;
- disconnect: () => Promise < void > ;
- destroy: () => Promise < void > ;
- logout: () => Promise < void > ;
- }
-
- /**
- * No session restoring functionality
- * Will need to authenticate via QR code every time
- */
- export class NoAuth extends AuthStrategy {}
-
- /**
- * Local directory-based authentication
- */
- export class LocalAuth extends AuthStrategy {
- public clientId ? : string;
- public dataPath ? : string;
- constructor(options ? : {
- clientId ? : string;dataPath ? : string
- });
- }
-
- /**
- * Remote-based authentication
- */
- export class RemoteAuth extends AuthStrategy {
- public clientId ? : string;
- public dataPath ? : string;
- constructor(options ? : {
- store: Store;
- clientId ? : string;
- dataPath ? : string;
- backupSyncIntervalMs: number;
- });
- }
-
- /**
- * Remote store interface
- */
- export interface Store {
- sessionExists: (options: {
- session: string
- }) => Promise < boolean > | boolean;
- delete: (options: {
- session: string
- }) => Promise < any > | any;
- save: (options: {
- session: string
- }) => Promise < any > | any;
- extract: (options: {
- session: string;path: string
- }) => Promise < any > | any;
- }
-
- /**
- * Legacy session auth strategy
- * Not compatible with multi-device accounts.
- */
- export class LegacySessionAuth extends AuthStrategy {
- constructor(options ? : {
- session ? : ClientSession;
- restartOnAuthFail ? : boolean;
- });
- }
-
- /**
- * Represents a WhatsApp client session
- */
- export interface ClientSession {
- WABrowserId: string;
- WASecretBundle: string;
- WAToken1: string;
- WAToken2: string;
- }
-
- /**
- * @deprecated
- */
- export interface BatteryInfo {
- /** The current battery percentage */
- battery: number;
- /** Indicates if the phone is plugged in (true) or not (false) */
- plugged: boolean;
- }
-
- export interface CreateGroupResult {
- /** ID for the group that was just created */
- gid: string;
- /** participants that were not added to the group.
- * Keys represent the ID for participant that was not added and its value is a status code
- * that represents the reason why participant could not be added.
- * This is usually 403 if the user's privacy settings don't allow you to add them to groups. */
- missingParticipants: Record < string,
- string > ;
- }
-
- export interface GroupNotification {
- /** ContactId for the user that produced the GroupNotification */
- author: string;
- /** Extra content */
- body: string;
- /** ID for the Chat that this groupNotification was sent for */
- chatId: string;
- /** ID that represents the groupNotification
- * @todo create a more specific type for the id object */
- id: object;
- /** Contact IDs for the users that were affected by this GroupNotification */
- recipientIds: string[];
- /** Unix timestamp for when the groupNotification was created */
- timestamp: number;
- /** GroupNotification type */
- type: GroupNotificationTypes;
-
- /** Returns the Chat this GroupNotification was sent in */
- getChat: () => Promise < Chat > ;
- /** Returns the Contact this GroupNotification was produced by */
- getContact: () => Promise < Contact > ;
- /** Returns the Contacts affected by this GroupNotification */
- getRecipients: () => Promise < Contact[] > ;
- /** Sends a message to the same chat this GroupNotification was produced in */
- reply: (
- content: MessageContent,
- options ? : MessageSendOptions
- ) => Promise < Message > ;
- }
-
- /** whatsapp web url */
- export const WhatsWebURL: string;
-
- /** default client options */
- export const DefaultOptions: ClientOptions;
-
- /** Chat types */
- export enum ChatTypes {
- SOLO = "solo",
- GROUP = "group",
- UNKNOWN = "unknown",
- }
-
- /** Events that can be emitted by the client */
- export enum Events {
- AUTHENTICATED = "authenticated",
- AUTHENTICATION_FAILURE = "auth_failure",
- READY = "ready",
- MESSAGE_RECEIVED = "message",
- MESSAGE_CREATE = "message_create",
- MESSAGE_REVOKED_EVERYONE = "message_revoke_everyone",
- MESSAGE_REVOKED_ME = "message_revoke_me",
- MESSAGE_ACK = "message_ack",
- MEDIA_UPLOADED = "media_uploaded",
- CONTACT_CHANGED = "contact_changed",
- GROUP_JOIN = "group_join",
- GROUP_LEAVE = "group_leave",
- GROUP_ADMIN_CHANGED = "group_admin_changed",
- GROUP_UPDATE = "group_update",
- QR_RECEIVED = "qr",
- LOADING_SCREEN = "loading_screen",
- DISCONNECTED = "disconnected",
- STATE_CHANGED = "change_state",
- BATTERY_CHANGED = "change_battery",
- REMOTE_SESSION_SAVED = "remote_session_saved",
- POLL_VOTE = "poll_vote",
- CALL = "call",
- }
-
- /** Group notification types */
- export enum GroupNotificationTypes {
- ADD = "add",
- INVITE = "invite",
- REMOVE = "remove",
- LEAVE = "leave",
- SUBJECT = "subject",
- DESCRIPTION = "description",
- PICTURE = "picture",
- ANNOUNCE = "announce",
- RESTRICT = "restrict",
- }
-
- /** Message ACK */
- export enum MessageAck {
- ACK_ERROR = -1,
- ACK_PENDING = 0,
- ACK_SERVER = 1,
- ACK_DEVICE = 2,
- ACK_READ = 3,
- ACK_PLAYED = 4,
- }
-
- /** Message types */
- export enum MessageTypes {
- TEXT = "chat",
- AUDIO = "audio",
- VOICE = "ptt",
- IMAGE = "image",
- VIDEO = "video",
- DOCUMENT = "document",
- STICKER = "sticker",
- LOCATION = "location",
- CONTACT_CARD = "vcard",
- CONTACT_CARD_MULTI = "multi_vcard",
- REVOKED = "revoked",
- ORDER = "order",
- PRODUCT = "product",
- PAYMENT = "payment",
- UNKNOWN = "unknown",
- GROUP_INVITE = "groups_v4_invite",
- LIST = "list",
- LIST_RESPONSE = "list_response",
- BUTTONS_RESPONSE = "buttons_response",
- BROADCAST_NOTIFICATION = "broadcast_notification",
- CALL_LOG = "call_log",
- CIPHERTEXT = "ciphertext",
- DEBUG = "debug",
- E2E_NOTIFICATION = "e2e_notification",
- GP2 = "gp2",
- GROUP_NOTIFICATION = "group_notification",
- HSM = "hsm",
- INTERACTIVE = "interactive",
- NATIVE_FLOW = "native_flow",
- NOTIFICATION = "notification",
- NOTIFICATION_TEMPLATE = "notification_template",
- OVERSIZED = "oversized",
- PROTOCOL = "protocol",
- REACTION = "reaction",
- TEMPLATE_BUTTON_REPLY = "template_button_reply",
- POLL_CREATION = "poll_creation",
- }
-
- /** Client status */
- export enum Status {
- INITIALIZING = 0,
- AUTHENTICATING = 1,
- READY = 3,
- }
-
- /** WhatsApp state */
- export enum WAState {
- CONFLICT = "CONFLICT",
- CONNECTED = "CONNECTED",
- DEPRECATED_VERSION = "DEPRECATED_VERSION",
- OPENING = "OPENING",
- PAIRING = "PAIRING",
- PROXYBLOCK = "PROXYBLOCK",
- SMB_TOS_BLOCK = "SMB_TOS_BLOCK",
- TIMEOUT = "TIMEOUT",
- TOS_BLOCK = "TOS_BLOCK",
- UNLAUNCHED = "UNLAUNCHED",
- UNPAIRED = "UNPAIRED",
- UNPAIRED_IDLE = "UNPAIRED_IDLE",
- }
-
- export type MessageInfo = {
- delivery: Array < {
- id: ContactId;t: number
- } > ;
- deliveryRemaining: number;
- played: Array < {
- id: ContactId;t: number
- } > ;
- playedRemaining: number;
- read: Array < {
- id: ContactId;t: number
- } > ;
- readRemaining: number;
- };
-
- export type InviteV4Data = {
- inviteCode: string;
- inviteCodeExp: number;
- groupId: string;
- groupName ? : string;
- fromId: string;
- toId: string;
- };
-
- /**
- * Represents a Message on WhatsApp
- *
- * @example
- * {
- * mediaKey: undefined,
- * id: {
- * fromMe: false,
- * remote: `554199999999@c.us`,
- * id: '1234567890ABCDEFGHIJ',
- * _serialized: `false_554199999999@c.us_1234567890ABCDEFGHIJ`
- * },
- * ack: -1,
- * hasMedia: false,
- * body: 'Hello!',
- * type: 'chat',
- * timestamp: 1591482682,
- * from: `554199999999@c.us`,
- * to: `554188888888@c.us`,
- * author: undefined,
- * isForwarded: false,
- * broadcast: false,
- * fromMe: false,
- * hasQuotedMsg: false,
- * hasReaction: false,
- * location: undefined,
- * mentionedIds: []
- * }
- */
- export interface Message {
- /** ACK status for the message */
- ack: MessageAck;
- /** If the message was sent to a group, this field will contain the user that sent the message. */
- author ? : string;
- /** String that represents from which device type the message was sent */
- deviceType: string;
- /** Message content */
- body: string;
- /** Indicates if the message was a broadcast */
- broadcast: boolean;
- /** Indicates if the message was a status update */
- isStatus: boolean;
- /** Indicates if the message is a Gif */
- isGif: boolean;
- /** Indicates if the message will disappear after it expires */
- isEphemeral: boolean;
- /** ID for the Chat that this message was sent to, except if the message was sent by the current user */
- from: string;
- /** Indicates if the message was sent by the current user */
- fromMe: boolean;
- /** Indicates if the message has media available for download */
- hasMedia: boolean;
- /** Indicates if the message was sent as a reply to another message */
- hasQuotedMsg: boolean;
- /** Indicates whether there are reactions to the message */
- hasReaction: boolean;
- /** Indicates the duration of the message in seconds */
- duration: string;
- /** ID that represents the message */
- id: MessageId;
- /** Indicates if the message was forwarded */
- isForwarded: boolean;
- /**
- * Indicates how many times the message was forwarded.
- * The maximum value is 127.
- */
- forwardingScore: number;
- /** Indicates if the message was starred */
- isStarred: boolean;
- /** Location information contained in the message, if the message is type "location" */
- location: Location;
- /** List of vCards contained in the message */
- vCards: string[];
- /** Invite v4 info */
- inviteV4 ? : InviteV4Data;
- /** MediaKey that represents the sticker 'ID' */
- mediaKey ? : string;
- /** Indicates the mentions in the message body. */
- mentionedIds: [];
- /** Unix timestamp for when the message was created */
- timestamp: number;
- /**
- * ID for who this message is for.
- * If the message is sent by the current user, it will be the Chat to which the message is being sent.
- * If the message is sent by another user, it will be the ID for the current user.
- */
- to: string;
- /** Message type */
- type: MessageTypes;
- /** Links included in the message. */
- links: Array < {
- link: string;
- isSuspicious: boolean;
- } > ;
- /** Order ID */
- orderId: string;
- /** title */
- title ? : string;
- /** description*/
- description ? : string;
- /** Business Owner JID */
- businessOwnerJid ? : string;
- /** Product JID */
- productId ? : string;
- /** Message buttons */
- dynamicReplyButtons ? : object;
- /** Selected button ID */
- 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.
- */
- /** Avaiaible poll voting options */
- pollOptions: string[];
- /** The current poll votes, refresh with .refreshPollVotes() */
- pollVotes: PollVote[];
- reload: () => Promise < Message > ;
- /** Accept the Group V4 Invite in message */
- acceptGroupV4Invite: () => Promise < {
- status: number
- } > ;
- /** Deletes the message from the chat */
- delete: (everyone ? : boolean) => Promise < void > ;
- /** Downloads and returns the attached message media */
- downloadMedia: () => Promise < MessageMedia > ;
- /** Returns the Chat this message was sent in */
- getChat: () => Promise < Chat > ;
- /** Returns the Contact this message was sent from */
- getContact: () => Promise < Contact > ;
- /** Returns the Contacts mentioned in this message */
- getMentions: () => Promise < Contact[] > ;
- /** Returns the quoted message, if any */
- getQuotedMessage: () => Promise < Message > ;
- /**
- * Sends a message as a reply to this message.
- * If chatId is specified, it will be sent through the specified Chat.
- * If not, it will send the message in the same Chat as the original message was sent.
- */
- reply: (
- content: MessageContent,
- chatId ? : string,
- options ? : MessageSendOptions
- ) => Promise < Message > ;
- /** React to this message with an emoji*/
- react: (reaction: string) => Promise < void > ;
- /**
- * Forwards this message to another chat (that you chatted before, otherwise it will fail)
- */
- forward: (chat: Chat | string) => Promise < void > ;
- /** Star this message */
- star: () => Promise < void > ;
- /** Unstar this message */
- unstar: () => Promise < void > ;
- /** Get information about message delivery status */
- getInfo: () => Promise < MessageInfo | null > ;
- /**
- * Gets the order associated with a given message
- */
- getOrder: () => Promise < Order > ;
- /**
- * Gets the payment details associated with a given message
- */
- getPayment: () => Promise < Payment > ;
- /**
- * Refreshes the current poll votes, only works with a poll_creation message
- */
- refreshPollVotes: () => Promise < void > ;
- /**
- * Vote on the poll, only works with a poll_creation message
- * @param {Array} selectedOptions The selected options from .pollOptions
- */
- vote: (selectedOptions: string[]) => Promise < void > ;
- /**
- * Gets the reactions associated with the given message
- */
- getReactions: () => Promise < ReactionList[] > ;
- }
-
- /** ID that represents a message */
- export interface MessageId {
- fromMe: boolean;
- remote: string;
- id: string;
- _serialized: string;
- }
-
- /** Location information */
- export class Location {
- description ? : string | null;
- latitude: string;
- longitude: string;
-
- constructor(latitude: number, longitude: number, description ? : string);
- }
-
- export interface Label {
- /** Label name */
- name: string;
- /** Label ID */
- id: string;
- /** Color assigned to the label */
- hexColor: string;
-
- /** Get all chats that have been assigned this Label */
- getChats: () => Promise < Chat[] > ;
- }
-
- /** Options for sending a message */
- export interface MessageSendOptions {
- /** Show links preview. Has no effect on multi-device accounts. */
- linkPreview ? : boolean;
- /** Send audio as voice message */
- sendAudioAsVoice ? : boolean;
- /** Send video as gif */
- sendVideoAsGif ? : boolean;
- /** Send media as sticker */
- sendMediaAsSticker ? : boolean;
- /** Send media as document */
- sendMediaAsDocument ? : boolean;
- /** Automatically parse vCards and send them as contacts */
- parseVCards ? : boolean;
- /** Image or videos caption */
- caption ? : string;
- /** Id of the message that is being quoted (or replied to) */
- quotedMessageId ? : string;
- /** Contacts that are being mentioned in the message */
- mentions ? : Contact[];
- /** Send 'seen' status */
- sendSeen ? : boolean;
- /** Media to be sent */
- media ? : MessageMedia;
- /** Extra options */
- extra ? : any;
- /** Sticker name, if sendMediaAsSticker is true */
- stickerName ? : string;
- /** Sticker author, if sendMediaAsSticker is true */
- stickerAuthor ? : string;
- /** Sticker categories, if sendMediaAsSticker is true */
- stickerCategories ? : string[];
- }
-
- export interface MediaFromURLOptions {
- client ? : Client;
- filename ? : string;
- unsafeMime ? : boolean;
- reqOptions ? : AxiosInterceptorOptions;
- }
-
- /** Media attached to a message */
- export class MessageMedia {
- /** MIME type of the attachment */
- mimetype: string;
- /** Base64-encoded data of the file */
- data: string;
- /** Document file name. Value can be null */
- filename ? : string | null;
- /** Document file size in bytes. Value can be null. */
- filesize ? : number | null;
-
- /**
- * @param {string} mimetype MIME type of the attachment
- * @param {string} data Base64-encoded data of the file
- * @param {?string} filename Document file name. Value can be null
- * @param {?number} filesize Document file size in bytes. Value can be null.
- */
- constructor(
- mimetype: string,
- data: string,
- filename ? : string | null,
- filesize ? : number | null
- );
-
- /** Creates a MessageMedia instance from a local file path */
- static fromFilePath: (filePath: string) => MessageMedia;
-
- /** Creates a MessageMedia instance from a URL */
- static fromUrl: (
- url: string,
- options ? : MediaFromURLOptions
- ) => Promise < MessageMedia > ;
- }
-
- export type MessageContent = |
- string |
- MessageMedia |
- Location |
- Contact |
- Contact[] |
- List |
- Buttons;
-
- /**
- * Represents a Contact on WhatsApp
- *
- * @example
- * {
- * id: {
- * server: 'c.us',
- * user: '554199999999',
- * _serialized: `554199999999@c.us`
- * },
- * number: '554199999999',
- * isBusiness: false,
- * isEnterprise: false,
- * labels: [],
- * name: undefined,
- * pushname: 'John',
- * sectionHeader: undefined,
- * shortName: undefined,
- * statusMute: false,
- * type: 'in',
- * verifiedLevel: undefined,
- * verifiedName: undefined,
- * isMe: false,
- * isUser: true,
- * isGroup: false,
- * isWAContact: true,
- * isMyContact: false
- * }
- */
- export interface Contact {
- /** Contact's phone number */
- number: string;
- /** Indicates if the contact is a business contact */
- isBusiness: boolean;
- /** ID that represents the contact */
- id: ContactId;
- /** Indicates if the contact is an enterprise contact */
- isEnterprise: boolean;
- /** Indicates if the contact is a group contact */
- isGroup: boolean;
- /** Indicates if the contact is the current user's contact */
- isMe: boolean;
- /** Indicates if the number is saved in the current phone's contacts */
- isMyContact: boolean;
- /** Indicates if the contact is a user contact */
- isUser: boolean;
- /** Indicates if the number is registered on WhatsApp */
- isWAContact: boolean;
- /** Indicates if you have blocked this contact */
- isBlocked: boolean;
- /** @todo verify labels type. didn't have any documentation */
- labels ? : string[];
- /** The contact's name, as saved by the current user */
- name ? : string;
- /** The name that the contact has configured to be shown publically */
- pushname: string;
- /** @todo missing documentation */
- sectionHeader: string;
- /** A shortened version of name */
- shortName ? : string;
- /** Indicates if the status from the contact is muted */
- statusMute: boolean;
- /** @todo missing documentation */
- type: string;
- /** @todo missing documentation */
- verifiedLevel ? : undefined;
- /** @todo missing documentation */
- verifiedName ? : undefined;
-
- /** Returns the contact's profile picture URL, if privacy settings allow it */
- getProfilePicUrl: () => Promise < string > ;
-
- /** Returns the Chat that corresponds to this Contact.
- * Will return null when getting chat for currently logged in user.
- */
- getChat: () => Promise < Chat > ;
-
- /** Returns the contact's countrycode, (1541859685@c.us) => (1) */
- getCountryCode(): Promise < string > ;
-
- /** Returns the contact's formatted phone number, (12345678901@c.us) => (+1 (234) 5678-901) */
- getFormattedNumber(): Promise < string > ;
-
- /** Blocks this contact from WhatsApp */
- block: () => Promise < boolean > ;
-
- /** Unlocks this contact from WhatsApp */
- unblock: () => Promise < boolean > ;
-
- /** 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[] > ;
- }
-
- export interface ContactId {
- server: string;
- user: string;
- _serialized: string;
- }
-
- export interface BusinessContact extends Contact {
- /**
- * The contact's business profile
- * @todo add a more specific type for the object
- */
- businessProfile: object;
- }
-
- export interface PrivateContact extends Contact {}
-
- /**
- * Represents a Chat on WhatsApp
- *
- * @example
- * {
- * id: {
- * server: 'c.us',
- * user: '554199999999',
- * _serialized: `554199999999@c.us`
- * },
- * name: '+55 41 9999-9999',
- * isGroup: false,
- * isReadOnly: false,
- * unreadCount: 6,
- * timestamp: 1591484087,
- * archived: false
- * }
- */
- export class Chat {
- /** Indicates if the Chat is archived */
- archived: boolean;
- /** ID that represents the chat */
- id: ChatId;
- /** Indicates if the Chat is a Group Chat */
- isGroup: boolean;
- /** Indicates if the Chat is readonly */
- isReadOnly: boolean;
- /** Indicates if the Chat is muted */
- isMuted: boolean;
- /** Unix timestamp for when the mute expires */
- muteExpiration: number;
- /** Title of the chat */
- name: string;
- /** Unix timestamp for when the last activity occurred */
- timestamp: number;
- /** Amount of messages unread */
- unreadCount: number;
- /** Last message fo chat */
- lastMessage: Message;
-
- /** Archives this chat */
- archive: () => Promise < void > ;
- /** Pins this chat and returns its new Pin state */
- pin: () => Promise < boolean > ;
- /** Unpins this chat and returns its new Pin state */
- unpin: () => Promise < boolean > ;
- /** Clears all messages from the chat */
- clearMessages: () => Promise < boolean > ;
- /** Stops typing or recording in chat immediately. */
- clearState: () => Promise < boolean > ;
- /** Deletes the chat */
- delete: () => Promise < boolean > ;
- /** Loads chat messages, sorted from earliest to latest. */
- fetchMessages: (searchOptions: MessageSearchOptions) => Promise < Message[] > ;
- /** Mutes this chat forever, unless a date is specified */
- mute: (unmuteDate ? : Date) => Promise < void > ;
- /** Send a message to this chat */
- sendMessage: (
- content: MessageContent,
- options ? : MessageSendOptions
- ) => Promise < Message > ;
- /** Set the message as seen */
- sendSeen: () => Promise < void > ;
- /** Simulate recording audio in chat. This will last for 25 seconds */
- sendStateRecording: () => Promise < void > ;
- /** Simulate typing in chat. This will last for 25 seconds. */
- sendStateTyping: () => Promise < void > ;
- /** un-archives this chat */
- unarchive: () => Promise < void > ;
- /** Unmutes this chat */
- unmute: () => Promise < void > ;
- /** Returns the Contact that corresponds to this Chat. */
- getContact: () => Promise < Contact > ;
- /** Marks this Chat as unread */
- markUnread: () => Promise < void > ;
- /** Returns array of all Labels assigned to this Chat */
- getLabels: () => Promise < Label[] > ;
- /** Add or remove labels to this Chat */
- changeLabels: (labelIds: Array < string | number > ) => Promise < void > ;
- }
-
- export interface MessageSearchOptions {
- /**
- * The amount of messages to return. If no limit is specified, the available messages will be returned.
- * Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation.
- * Set this to Infinity to load all messages.
- */
- limit ? : number;
- /**
- * Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
- */
- fromMe ? : boolean;
- }
-
- /**
- * Id that represents the chat
- *
- * @example
- * id: {
- * server: 'c.us',
- * user: '554199999999',
- * _serialized: `554199999999@c.us`
- * },
- */
- export interface ChatId {
- /**
- * Whatsapp server domain
- * @example `c.us`
- */
- server: string;
- /**
- * User whatsapp number
- * @example `554199999999`
- */
- user: string;
- /**
- * Serialized id
- * @example `554199999999@c.us`
- */
- _serialized: string;
- }
-
- export interface PrivateChat extends Chat {}
-
- export type GroupParticipant = {
- id: ContactId;
- isAdmin: boolean;
- isSuperAdmin: boolean;
- };
-
- /** Promotes or demotes participants by IDs to regular users or admins */
- export type ChangeParticipantsPermissions = (
- participantIds: Array < string >
- ) => Promise < {
- status: number
- } > ;
-
- /** An object that handles the result of addParticipants method */
- export type AddParticipantsResult = {
- [participantId: string]: {
- code: number;
- message: string;
- /** @default false */
- isInviteV4Sent: boolean;
- };
- };
-
- /** AddParticipnats options */
- export type AddParticipnatsOptions = {
- /** @default 500 */
- sleep ? : number;
- /** @default true */
- autoSendInviteV4 ? : boolean;
- /** @default '' */
- comment ? : string;
- };
-
- export interface GroupChat extends Chat {
- /** Group owner */
- owner: ContactId;
- /** Date at which the group was created */
- createdAt: Date;
- /** Group description */
- description: string;
- /** Group participants */
- participants: Array < GroupParticipant > ;
- /** Adds a list of participants by ID to the group */
- addParticipants: (
- participantIds: string[],
- options ? : AddParticipnatsOptions
- ) => Promise < AddParticipantsResult | string > ;
- /** Removes a list of participants by ID to the group */
- removeParticipants: (
- participantIds: string[]
- ) => Promise < {
- status: number
- } > ;
- /** Promotes participants by IDs to admins */
- promoteParticipants: ChangeParticipantsPermissions;
- /** Demotes participants by IDs to regular users */
- demoteParticipants: ChangeParticipantsPermissions;
- /** Updates the group subject */
- setSubject: (subject: string) => Promise < boolean > ;
- /** Updates the group description */
- 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} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
- */
- setMessagesAdminsOnly: (adminsOnly ? : boolean) => Promise < boolean > ;
- /**
- * Updates the group settings to only allow admins to edit group info (title, description, photo).
- * @param {boolean} [adminsOnly=true] Enable or disable this option
- * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
- */
- setInfoAdminsOnly: (adminsOnly ? : boolean) => Promise < boolean > ;
- /** Gets the invite code for a specific group */
- getInviteCode: () => Promise < string > ;
- /** Invalidates the current group invite code and generates a new one */
- revokeInvite: () => Promise < void > ;
- /** Makes the bot leave the group */
- leave: () => Promise < void > ;
- /** Sets the group's picture.*/
- setPicture: (media: MessageMedia) => Promise < boolean > ;
- /** Deletes the group's picture */
- deletePicture: () => Promise < boolean > ;
- }
-
- /**
- * Represents the metadata associated with a given product
- *
- */
- export interface ProductMetadata {
- /** Product Id */
- id: string;
- /** Product Name */
- name: string;
- /** Product Description */
- description: string;
- /** Retailer ID */
- retailer_id ? : string;
- }
-
- /**
- * Represents a Product on Whatsapp
- * @example
- * {
- * "id": "123456789",
- * "price": "150000",
- * "thumbnailId": "123456789",
- * "thumbnailUrl": "https://mmg.whatsapp.net",
- * "currency": "GTQ",
- * "name": "Store Name",
- * "quantity": 1
- * }
- */
- export interface Product {
- /** Product Id */
- id: string;
- /** Price */
- price ? : string;
- /** Product Thumbnail*/
- thumbnailUrl: string;
- /** Currency */
- currency: string;
- /** Product Name */
- name: string;
- /** Product Quantity*/
- quantity: number;
- /** Gets the Product metadata */
- getData: () => Promise < ProductMetadata > ;
- }
-
- /**
- * Represents a Order on WhatsApp
- *
- * @example
- * {
- * "products": [
- * {
- * "id": "123456789",
- * "price": "150000",
- * "thumbnailId": "123456789",
- * "thumbnailUrl": "https://mmg.whatsapp.net",
- * "currency": "GTQ",
- * "name": "Store Name",
- * "quantity": 1
- * }
- * ],
- * "subtotal": "150000",
- * "total": "150000",
- * "currency": "GTQ",
- * "createdAt": 1610136796,
- * "sellerJid": "55555555@s.whatsapp.net"
- * }
- */
- export interface Order {
- /** List of products*/
- products: Array < Product > ;
- /** Order Subtotal */
- subtotal: string;
- /** Order Total */
- total: string;
- /** Order Currency */
- currency: string;
- /** Order Created At*/
- createdAt: number;
- }
-
- /**
- * Represents a Payment on WhatsApp
- *
- * @example
- * {
- * id: {
- * fromMe: true,
- * remote: {
- * server: 'c.us',
- * user: '5511999999999',
- * _serialized: '5511999999999@c.us'
- * },
- * id: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
- * _serialized: 'true_5511999999999@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- * },
- * paymentCurrency: 'BRL',
- * paymentAmount1000: 1000,
- * paymentMessageReceiverJid: {
- * server: 'c.us',
- * user: '5511999999999',
- * _serialized: '5511999999999@c.us'
- * },
- * paymentTransactionTimestamp: 1623463058,
- * paymentStatus: 4,
- * paymentTxnStatus: 4,
- * paymentNote: 'note'
- * }
- */
- export interface Payment {
- /** Payment Id*/
- id: object;
- /** Payment currency */
- paymentCurrency: string;
- /** Payment ammount */
- paymentAmount1000: number;
- /** Payment receiver */
- paymentMessageReceiverJid: object;
- /** Payment transaction timestamp */
- paymentTransactionTimestamp: number;
- /** Payment paymentStatus */
- paymentStatus: number;
- /** Integer that represents the payment Text */
- paymentTxnStatus: number;
- /** The note sent with the payment */
- paymentNote: string;
- }
-
- /**
- * Represents a Call on WhatsApp
- *
- * @example
- * Call {
- * id: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
- * from: '5511999999@c.us',
- * timestamp: 1625003709,
- * isVideo: false,
- * isGroup: false,
- * fromMe: false,
- * canHandleLocally: false,
- * webClientShouldHandle: false,
- * participants: []
- * }
- */
- export interface Call {
- /** Call Id */
- id: string;
- /** from */
- from ? : string;
- /** Unix timestamp for when the call was created*/
- timestamp: number;
- /** Is video */
- isVideo: boolean;
- /** Is Group */
- isGroup: boolean;
- /** Indicates if the call was sent by the current user */
- fromMe: boolean;
- /** indicates if the call can be handled in waweb */
- canHandleLocally: boolean;
- /** indicates if the call should be handled in waweb */
- webClientShouldHandle: boolean;
- /** Object with participants */
- participants: object;
-
- /** Reject the call */
- reject: () => Promise < void > ;
- }
-
- /** Message type List */
- export class List {
- body: string;
- buttonText: string;
- sections: Array < any > ;
- title ? : string | null;
- footer ? : string | null;
-
- constructor(
- body: string,
- buttonText: string,
- sections: Array < any > ,
- title ? : string | null,
- footer ? : string | null
- );
- }
-
- /** Message type Buttons */
- export class Buttons {
- body: string | MessageMedia;
- buttons: Array < {
- buttonId: string;
- buttonText: {
- displayText: string
- };
- type: number;
- } > ;
- title ? : string | null;
- footer ? : string | null;
-
- constructor(
- body: string,
- buttons: Array < {
- id ? : string;body: string
- } > ,
- title ? : string | null,
- footer ? : string | null
- );
- }
-
- /** Message type Reaction */
- export class Reaction {
- id: MessageId;
- orphan: number;
- orphanReason ? : string;
- timestamp: number;
- reaction: string;
- read: boolean;
- msgId: MessageId;
- senderId: string;
- ack ? : number;
- }
-
- export class PollVote {
- selectedOptions: string[];
- sender: string;
- senderTimestampMs: number;
- }
-
- export type ReactionList = {
- id: string;
- aggregateEmoji: string;
- hasReactionByMe: boolean;
- senders: Array < Reaction > ;
- };
-
- export interface CallOptions {
- isGroup: boolean;
- }
+ /** Unmutes the Chat */
+ unmuteChat(chatId: string): Promise;
+
+ /** Sets the current user's profile picture */
+ setProfilePicture(media: MessageMedia): Promise;
+
+ /** Deletes the current user's profile picture */
+ deleteProfilePicture(): Promise;
+
+ /** Send a call to someone */
+ sendCall(chatId: string, options: CallOptions): Promise;
+
+ /** Hang up the call in progress */
+ endCall(chatId: string): Promise;
+
+ /** Receive a phone call from someone */
+ acceptCall(chatId: string): Promise;
+
+ /** Displays last seen status (Conditions : No Privacy) */
+ getLastSeen(chatId: string): Promise;
+
+ /** Archives or No all selected conversation, 'chat' or 'group' except pinned ones */
+ archiveAll(type: string, status: boolean): Promise;
+
+ /** Mute or No all selected conversation, 'chat' or 'group' except pinned ones */
+ muteAll(type: string, status: boolean): Promise;
+
+ /** Displays connection status */
+ getHost(): Promise;
+
+ /** Change the display theme 'light' or 'dark' */
+ setTheme(type: string): Promise;
+
+ /** get display theme */
+ getTheme(): Promise;
+
+ /** Delete all messages */
+ clearMessage(chatId: string): Promise;
+
+ /** fetch stories user */
+ getStories(chatId: string): Promise;
+
+ /** get contact by name */
+ getContactByName(name: string): Promise;
+
+ /** send polling message */
+ sendPoll(
+ chatId: string,
+ name: string,
+ choices: Array,
+ options: MessageSendOptions
+ ): Promise;
+
+ /** read status user */
+ sendReadStatus(chatId: string, msgId: string): Promise;
+
+ /** Generic event */
+ on(event: string, listener: (...args: any) => void): this;
+
+ /** Emitted when there has been an error while trying to restore an existing session */
+ on(event: "auth_failure", listener: (message: string) => void): this;
+
+ /** Emitted when authentication is successful */
+ on(
+ event: "authenticated",
+ listener: (
+ /**
+ * Object containing session information, when using LegacySessionAuth. Can be used to restore the session
+ */
+ session?: ClientSession
+ ) => void
+ ): this;
+
+ /**
+ * 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 */
+ on(
+ event: "change_state",
+ listener: (
+ /** the new connection state */
+ state: WAState
+ ) => void
+ ): this;
+
+ /** Emitted when the client has been disconnected */
+ on(
+ event: "disconnected",
+ listener: (
+ /** reason that caused the disconnect */
+ reason: WAState | "NAVIGATION"
+ ) => void
+ ): this;
+
+ /** Emitted when a user joins the chat via invite link or is added by an admin */
+ on(
+ event: "group_join",
+ listener: (
+ /** GroupNotification with more information about the action */
+ notification: GroupNotification
+ ) => void
+ ): this;
+
+ /** Emitted when a user leaves the chat or is removed by an admin */
+ on(
+ event: "group_leave",
+ listener: (
+ /** GroupNotification with more information about the action */
+ notification: GroupNotification
+ ) => void
+ ): this;
+
+ /** Emitted when a current user is promoted to an admin or demoted to a regular user */
+ on(
+ event: "group_admin_changed",
+ listener: (
+ /** GroupNotification with more information about the action */
+ notification: GroupNotification
+ ) => void
+ ): this;
+
+ /** Emitted when group settings are updated, such as subject, description or picture */
+ on(
+ event: "group_update",
+ listener: (
+ /** GroupNotification with more information about the action */
+ notification: GroupNotification
+ ) => void
+ ): this;
+
+ /** Emitted when a contact or a group participant changed their phone number. */
+ on(
+ event: "contact_changed",
+ listener: (
+ /** Message with more information about the event. */
+ message: Message,
+ /** Old user's id. */
+ oldId: String,
+ /** New user's id. */
+ newId: String,
+ /** Indicates if a contact or a group participant changed their phone number. */
+ isContact: Boolean
+ ) => void
+ ): this;
+
+ /** Emitted when media has been uploaded for a message sent by the client */
+ on(
+ event: "media_uploaded",
+ listener: (
+ /** The message with media that was uploaded */
+ message: Message
+ ) => void
+ ): this;
+
+ /** Emitted when a new message is received */
+ on(
+ event: "message",
+ listener: (
+ /** The message that was received */
+ message: Message
+ ) => void
+ ): this;
+
+ /** Emitted when an ack event occurrs on message type */
+ on(
+ event: "message_ack",
+ listener: (
+ /** The message that was affected */
+ message: Message,
+ /** The new ACK value */
+ ack: MessageAck
+ ) => void
+ ): this;
+
+ /** Emitted when a chat unread count changes */
+ on(
+ event: "unread_count",
+ listener: (
+ /** The chat that was affected */
+ chat: Chat
+ ) => void
+ ): this;
+
+ /** Emitted when a new message is created, which may include the current user's own messages */
+ on(
+ event: "message_create",
+ listener: (
+ /** The message that was created */
+ message: Message
+ ) => void
+ ): this;
+
+ /** Emitted when a message is deleted for everyone in the chat */
+ on(
+ event: "message_revoke_everyone",
+ listener: (
+ /** The message that was revoked, in its current state. It will not contain the original message's data */
+ message: Message,
+ /**The message that was revoked, before it was revoked.
+ * It will contain the message's original data.
+ * Note that due to the way this data is captured,
+ * it may be possible that this param will be undefined. */
+ revoked_msg?: Message | null
+ ) => void
+ ): this;
+
+ /** Emitted when a message is deleted by the current user */
+ on(
+ event: "message_revoke_me",
+ listener: (
+ /** The message that was revoked */
+ message: Message
+ ) => void
+ ): this;
+
+ /** Emitted when a reaction is sent, received, updated or removed */
+ on(
+ event: "message_reaction",
+ listener: (
+ /** The reaction object */
+ reaction: Reaction
+ ) => void
+ ): this;
+
+ /** Emitted when a chat is removed */
+ on(
+ event: "chat_removed",
+ listener: (
+ /** The chat that was removed */
+ chat: Chat
+ ) => void
+ ): this;
+
+ /** Emitted when a chat is archived/unarchived */
+ on(
+ event: "chat_archived",
+ listener: (
+ /** The chat that was archived/unarchived */
+ chat: Chat,
+ /** State the chat is currently in */
+ currState: boolean,
+ /** State the chat was previously in */
+ prevState: boolean
+ ) => void
+ ): this;
+
+ /** Emitted when loading screen is appearing */
+ on(
+ event: "loading_screen",
+ listener: (percent: string, message: string) => void
+ ): this;
+
+ /** Emitted when the QR code is received */
+ on(
+ event: "qr",
+ listener: (
+ /** qr code string
+ * @example ```1@9Q8tWf6bnezr8uVGwVCluyRuBOJ3tIglimzI5dHB0vQW2m4DQ0GMlCGf,f1/vGcW4Z3vBa1eDNl3tOjWqLL5DpYTI84DMVkYnQE8=,ZL7YnK2qdPN8vKo2ESxhOQ==``` */
+ qr: string
+ ) => void
+ ): this;
+
+ /* login with code */
+ on(event: "code", listener: (code: string) => void): this;
+
+ /** Emitted when a call is received */
+ on(
+ event: "call",
+ listener: (
+ /** The call that started */
+ call: Call
+ ) => void
+ ): this;
+
+ /** Emitted when the client has initialized and is ready to receive messages */
+ on(event: "ready", listener: () => void): this;
+
+ /** Emitted when the RemoteAuth session is saved successfully on the external Database */
+ on(event: "remote_session_saved", listener: () => void): this;
+
+ /** Emitted when a poll vote is received */
+ on(
+ event: "poll_vote",
+ listener: (
+ /** The poll vote */
+ vote: PollVote
+ ) => void
+ ): this;
+ }
+
+ /** 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. Not available in multi-device.
+ * @deprecated
+ */
+ phone: ClientInfoPhone;
+ /** Platform the phone is running on */
+ platform: string;
+ /** Name configured to be shown in push notifications */
+ pushname: string;
+
+ /** Get current battery percentage and charging status for the attached device */
+ getBatteryStatus: () => Promise;
+ }
+
+ /**
+ * Information about the phone this client is connected to
+ * @deprecated
+ */
+ export interface ClientInfoPhone {
+ /** WhatsApp Version running on the phone */
+ wa_version: string;
+ /** OS Version running on the phone (iOS or Android version) */
+ os_version: string;
+ /** Device manufacturer */
+ device_manufacturer: string;
+ /** Device model */
+ device_model: string;
+ /** OS build number */
+ os_build_number: string;
+ }
+
+ /** Options for initializing the whatsapp client */
+ export interface ClientOptions {
+ /** Timeout for authentication selector in playwright
+ * @default 0 */
+ authTimeoutMs?: number;
+ /** playwright launch options. View docs here: https://github.com/microsoft/playwright/ */
+ playwright?: playwright.LaunchOptions & playwright.ConnectOptions;
+ /** Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. */
+ authStrategy?: AuthStrategy;
+ /** The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved. */
+ webVersion?: string,
+ /** How many times should the qrcode be refreshed before giving up
+ * @default 0 (disabled) */
+ qrMaxRetries?: number;
+ /**
+ * @deprecated This option should be set directly on the LegacySessionAuth
+ */
+
+ /* linking method */
+ linkingMethod: LinkingMethod;
+
+ restartOnAuthFail?: boolean;
+ /**
+ * @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
+ */
+ session?: ClientSession;
+ /** If another whatsapp web session is detected (another browser), take over the session in the current browser
+ * @default false */
+ takeoverOnConflict?: boolean;
+ /** How much time to wait before taking over the session
+ * @default 0 */
+ takeoverTimeoutMs?: number;
+ /** User agent to use in playwright.
+ * @default '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' */
+ userAgent?: string;
+ /** Ffmpeg path to use when formating videos to webp while sending stickers
+ * @default 'ffmpeg' */
+ ffmpegPath?: string;
+ /** Object with proxy autentication requirements @default: undefined */
+ proxyAuthentication?: {
+ username: string; password: string
+ } | undefined;
+ }
+
+ /**
+ * Base class which all authentication strategies extend
+ */
+ export abstract class AuthStrategy {
+ setup: (client: Client) => void;
+ beforeBrowserInitialized: () => Promise;
+ afterBrowserInitialized: () => Promise;
+ onAuthenticationNeeded: () => Promise<{
+ failed?: boolean;
+ restart?: boolean;
+ failureEventPayload?: any;
+ }>;
+ getAuthEventPayload: () => Promise;
+ afterAuthReady: () => Promise;
+ disconnect: () => Promise;
+ destroy: () => Promise;
+ logout: () => Promise;
+ }
+
+ /**
+ * No session restoring functionality
+ * Will need to authenticate via QR code every time
+ */
+ export class NoAuth extends AuthStrategy { }
+
+ /**
+ * Local directory-based authentication
+ */
+ export class LocalAuth extends AuthStrategy {
+ public clientId?: string;
+ public dataPath?: string;
+ constructor(options?: {
+ clientId?: string; dataPath?: string
+ });
+ }
+
+ /**
+ * Remote-based authentication
+ */
+ export class RemoteAuth extends AuthStrategy {
+ public clientId?: string;
+ public dataPath?: string;
+ constructor(options?: {
+ store: Store;
+ clientId?: string;
+ dataPath?: string;
+ backupSyncIntervalMs: number;
+ });
+ }
+
+ /**
+ * Remote store interface
+ */
+ export interface Store {
+ sessionExists: (options: {
+ session: string
+ }) => Promise | boolean;
+ delete: (options: {
+ session: string
+ }) => Promise | any;
+ save: (options: {
+ session: string
+ }) => Promise | any;
+ extract: (options: {
+ session: string; path: string
+ }) => Promise | any;
+ }
+
+ /**
+ * Legacy session auth strategy
+ * Not compatible with multi-device accounts.
+ */
+ export class LegacySessionAuth extends AuthStrategy {
+ constructor(options?: {
+ session?: ClientSession;
+ restartOnAuthFail?: boolean;
+ });
+ }
+
+ /**
+ * Represents a WhatsApp client session
+ */
+ export interface ClientSession {
+ WABrowserId: string;
+ WASecretBundle: string;
+ WAToken1: string;
+ WAToken2: string;
+ }
+
+ /**
+ * @deprecated
+ */
+ export interface BatteryInfo {
+ /** The current battery percentage */
+ battery: number;
+ /** Indicates if the phone is plugged in (true) or not (false) */
+ plugged: boolean;
+ }
+
+ /** An object that handles options for group creation */
+ export interface CreateGroupOptions {
+ /**
+ * The number of seconds for the messages to disappear in the group,
+ * won't take an effect if the group is been creating with myself only
+ * @default 0
+ */
+ messageTimer?: number
+ /**
+ * The ID of a parent community group to link the newly created group with,
+ * won't take an effect if the group is been creating with myself only
+ */
+ parentGroupId?: string
+ /** If true, the inviteV4 will be sent to those participants
+ * who have restricted others from being automatically added to groups,
+ * otherwise the inviteV4 won't be sent
+ * @default true
+ */
+ autoSendInviteV4?: boolean,
+ /**
+ * The comment to be added to an inviteV4 (empty string by default)
+ * @default ''
+ */
+ comment?: string
+ }
+
+ export interface CreateGroupResult {
+ /** ID for the group that was just created */
+ gid: string;
+ /** participants that were not added to the group.
+ * Keys represent the ID for participant that was not added and its value is a status code
+ * that represents the reason why participant could not be added.
+ * This is usually 403 if the user's privacy settings don't allow you to add them to groups. */
+ missingParticipants: Record;
+ }
+
+ export interface GroupNotification {
+ /** ContactId for the user that produced the GroupNotification */
+ author: string;
+ /** Extra content */
+ body: string;
+ /** ID for the Chat that this groupNotification was sent for */
+ chatId: string;
+ /** ID that represents the groupNotification
+ * @todo create a more specific type for the id object */
+ id: object;
+ /** Contact IDs for the users that were affected by this GroupNotification */
+ recipientIds: string[];
+ /** Unix timestamp for when the groupNotification was created */
+ timestamp: number;
+ /** GroupNotification type */
+ type: GroupNotificationTypes;
+
+ /** Returns the Chat this GroupNotification was sent in */
+ getChat: () => Promise;
+ /** Returns the Contact this GroupNotification was produced by */
+ getContact: () => Promise;
+ /** Returns the Contacts affected by this GroupNotification */
+ getRecipients: () => Promise;
+ /** Sends a message to the same chat this GroupNotification was produced in */
+ reply: (
+ content: MessageContent,
+ options?: MessageSendOptions
+ ) => Promise;
+ }
+
+ /** whatsapp web url */
+ export const WhatsWebURL: string;
+
+ /** default client options */
+ export const DefaultOptions: ClientOptions;
+
+ /** Chat types */
+ export enum ChatTypes {
+ SOLO = "solo",
+ GROUP = "group",
+ UNKNOWN = "unknown",
+ }
+
+ /** Events that can be emitted by the client */
+ export enum Events {
+ AUTHENTICATED = "authenticated",
+ AUTHENTICATION_FAILURE = "auth_failure",
+ READY = "ready",
+ MESSAGE_RECEIVED = "message",
+ MESSAGE_CREATE = "message_create",
+ MESSAGE_REVOKED_EVERYONE = "message_revoke_everyone",
+ MESSAGE_REVOKED_ME = "message_revoke_me",
+ MESSAGE_ACK = "message_ack",
+ MEDIA_UPLOADED = "media_uploaded",
+ CONTACT_CHANGED = "contact_changed",
+ GROUP_JOIN = "group_join",
+ GROUP_LEAVE = "group_leave",
+ GROUP_ADMIN_CHANGED = "group_admin_changed",
+ GROUP_UPDATE = "group_update",
+ QR_RECEIVED = "qr",
+ LOADING_SCREEN = "loading_screen",
+ DISCONNECTED = "disconnected",
+ STATE_CHANGED = "change_state",
+ BATTERY_CHANGED = "change_battery",
+ REMOTE_SESSION_SAVED = "remote_session_saved",
+ POLL_VOTE = "poll_vote",
+ CALL = "call",
+ }
+
+ /** Group notification types */
+ export enum GroupNotificationTypes {
+ ADD = "add",
+ INVITE = "invite",
+ REMOVE = "remove",
+ LEAVE = "leave",
+ SUBJECT = "subject",
+ DESCRIPTION = "description",
+ PICTURE = "picture",
+ ANNOUNCE = "announce",
+ RESTRICT = "restrict",
+ }
+
+ /** Message ACK */
+ export enum MessageAck {
+ ACK_ERROR = -1,
+ ACK_PENDING = 0,
+ ACK_SERVER = 1,
+ ACK_DEVICE = 2,
+ ACK_READ = 3,
+ ACK_PLAYED = 4,
+ }
+
+ /** Message types */
+ export enum MessageTypes {
+ TEXT = "chat",
+ AUDIO = "audio",
+ VOICE = "ptt",
+ IMAGE = "image",
+ VIDEO = "video",
+ DOCUMENT = "document",
+ STICKER = "sticker",
+ LOCATION = "location",
+ CONTACT_CARD = "vcard",
+ CONTACT_CARD_MULTI = "multi_vcard",
+ REVOKED = "revoked",
+ ORDER = "order",
+ PRODUCT = "product",
+ PAYMENT = "payment",
+ UNKNOWN = "unknown",
+ GROUP_INVITE = "groups_v4_invite",
+ LIST = "list",
+ LIST_RESPONSE = "list_response",
+ BUTTONS_RESPONSE = "buttons_response",
+ BROADCAST_NOTIFICATION = "broadcast_notification",
+ CALL_LOG = "call_log",
+ CIPHERTEXT = "ciphertext",
+ DEBUG = "debug",
+ E2E_NOTIFICATION = "e2e_notification",
+ GP2 = "gp2",
+ GROUP_NOTIFICATION = "group_notification",
+ HSM = "hsm",
+ INTERACTIVE = "interactive",
+ NATIVE_FLOW = "native_flow",
+ NOTIFICATION = "notification",
+ NOTIFICATION_TEMPLATE = "notification_template",
+ OVERSIZED = "oversized",
+ PROTOCOL = "protocol",
+ REACTION = "reaction",
+ TEMPLATE_BUTTON_REPLY = "template_button_reply",
+ POLL_CREATION = "poll_creation",
+ }
+
+ /** Client status */
+ export enum Status {
+ INITIALIZING = 0,
+ AUTHENTICATING = 1,
+ READY = 3,
+ }
+
+ /** WhatsApp state */
+ export enum WAState {
+ CONFLICT = "CONFLICT",
+ CONNECTED = "CONNECTED",
+ DEPRECATED_VERSION = "DEPRECATED_VERSION",
+ OPENING = "OPENING",
+ PAIRING = "PAIRING",
+ PROXYBLOCK = "PROXYBLOCK",
+ SMB_TOS_BLOCK = "SMB_TOS_BLOCK",
+ TIMEOUT = "TIMEOUT",
+ TOS_BLOCK = "TOS_BLOCK",
+ UNLAUNCHED = "UNLAUNCHED",
+ UNPAIRED = "UNPAIRED",
+ UNPAIRED_IDLE = "UNPAIRED_IDLE",
+ }
+
+ export type MessageInfo = {
+ delivery: Array<{
+ id: ContactId; t: number
+ }>;
+ deliveryRemaining: number;
+ played: Array<{
+ id: ContactId; t: number
+ }>;
+ playedRemaining: number;
+ read: Array<{
+ id: ContactId; t: number
+ }>;
+ readRemaining: number;
+ };
+
+ export type InviteV4Data = {
+ inviteCode: string;
+ inviteCodeExp: number;
+ groupId: string;
+ groupName?: string;
+ fromId: string;
+ toId: string;
+ };
+
+ /**
+ * Represents a Message on WhatsApp
+ *
+ * @example
+ * {
+ * mediaKey: undefined,
+ * id: {
+ * fromMe: false,
+ * remote: `554199999999@c.us`,
+ * id: '1234567890ABCDEFGHIJ',
+ * _serialized: `false_554199999999@c.us_1234567890ABCDEFGHIJ`
+ * },
+ * ack: -1,
+ * hasMedia: false,
+ * body: 'Hello!',
+ * type: 'chat',
+ * timestamp: 1591482682,
+ * from: `554199999999@c.us`,
+ * to: `554188888888@c.us`,
+ * author: undefined,
+ * isForwarded: false,
+ * broadcast: false,
+ * fromMe: false,
+ * hasQuotedMsg: false,
+ * hasReaction: false,
+ * location: undefined,
+ * mentionedIds: []
+ * }
+ */
+ export interface Message {
+ /** ACK status for the message */
+ ack: MessageAck;
+ /** If the message was sent to a group, this field will contain the user that sent the message. */
+ author?: string;
+ /** String that represents from which device type the message was sent */
+ deviceType: string;
+ /** Message content */
+ body: string;
+ /** Indicates if the message was a broadcast */
+ broadcast: boolean;
+ /** Indicates if the message was a status update */
+ isStatus: boolean;
+ /** Indicates if the message is a Gif */
+ isGif: boolean;
+ /** Indicates if the message will disappear after it expires */
+ isEphemeral: boolean;
+ /** ID for the Chat that this message was sent to, except if the message was sent by the current user */
+ from: string;
+ /** Indicates if the message was sent by the current user */
+ fromMe: boolean;
+ /** Indicates if the message has media available for download */
+ hasMedia: boolean;
+ /** Indicates if the message was sent as a reply to another message */
+ hasQuotedMsg: boolean;
+ /** Indicates whether there are reactions to the message */
+ hasReaction: boolean;
+ /** Indicates the duration of the message in seconds */
+ duration: string;
+ /** ID that represents the message */
+ id: MessageId;
+ /** Indicates if the message was forwarded */
+ isForwarded: boolean;
+ /**
+ * Indicates how many times the message was forwarded.
+ * The maximum value is 127.
+ */
+ forwardingScore: number;
+ /** Indicates if the message was starred */
+ isStarred: boolean;
+ /** Location information contained in the message, if the message is type "location" */
+ location: Location;
+ /** List of vCards contained in the message */
+ vCards: string[];
+ /** Invite v4 info */
+ inviteV4?: InviteV4Data;
+ /** MediaKey that represents the sticker 'ID' */
+ mediaKey?: string;
+ /** Indicates the mentions in the message body. */
+ mentionedIds: [];
+ /** Unix timestamp for when the message was created */
+ timestamp: number;
+ /**
+ * ID for who this message is for.
+ * If the message is sent by the current user, it will be the Chat to which the message is being sent.
+ * If the message is sent by another user, it will be the ID for the current user.
+ */
+ to: string;
+ /** Message type */
+ type: MessageTypes;
+ /** Links included in the message. */
+ links: Array<{
+ link: string;
+ isSuspicious: boolean;
+ }>;
+ /** Order ID */
+ orderId: string;
+ /** title */
+ title?: string;
+ /** description*/
+ description?: string;
+ /** Business Owner JID */
+ businessOwnerJid?: string;
+ /** Product JID */
+ productId?: string;
+ /** Message buttons */
+ dynamicReplyButtons?: object;
+ /** Selected button ID */
+ 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.
+ */
+ /** Avaiaible poll voting options */
+ pollOptions: string[];
+ /** The current poll votes, refresh with .refreshPollVotes() */
+ pollVotes: PollVote[];
+ reload: () => Promise;
+ /** Accept the Group V4 Invite in message */
+ acceptGroupV4Invite: () => Promise<{
+ status: number
+ }>;
+ /** Deletes the message from the chat */
+ delete: (everyone?: boolean) => Promise;
+ /** Downloads and returns the attached message media */
+ downloadMedia: () => Promise;
+ /** Returns the Chat this message was sent in */
+ getChat: () => Promise;
+ /** Returns the Contact this message was sent from */
+ getContact: () => Promise;
+ /** Returns the Contacts mentioned in this message */
+ getMentions: () => Promise;
+ /** Returns the quoted message, if any */
+ getQuotedMessage: () => Promise;
+ /**
+ * Sends a message as a reply to this message.
+ * If chatId is specified, it will be sent through the specified Chat.
+ * If not, it will send the message in the same Chat as the original message was sent.
+ */
+ reply: (
+ content: MessageContent,
+ chatId?: string,
+ options?: MessageSendOptions
+ ) => Promise;
+ /** React to this message with an emoji*/
+ react: (reaction: string) => Promise;
+ /**
+ * Forwards this message to another chat (that you chatted before, otherwise it will fail)
+ */
+ forward: (chat: Chat | string) => Promise;
+ /** Star this message */
+ star: () => Promise;
+ /** Unstar this message */
+ unstar: () => Promise;
+ /** Get information about message delivery status */
+ getInfo: () => Promise;
+ /**
+ * Gets the order associated with a given message
+ */
+ getOrder: () => Promise;
+ /**
+ * Gets the payment details associated with a given message
+ */
+ getPayment: () => Promise;
+ /**
+ * Refreshes the current poll votes, only works with a poll_creation message
+ */
+ refreshPollVotes: () => Promise;
+ /**
+ * Vote on the poll, only works with a poll_creation message
+ * @param {Array} selectedOptions The selected options from .pollOptions
+ */
+ vote: (selectedOptions: string[]) => Promise;
+ /**
+ * Gets the reactions associated with the given message
+ */
+ getReactions: () => Promise;
+ }
+
+ /** ID that represents a message */
+ export interface MessageId {
+ fromMe: boolean;
+ remote: string;
+ id: string;
+ _serialized: string;
+ }
+
+ /** Location information */
+ export class Location {
+ description?: string | null;
+ latitude: string;
+ longitude: string;
+
+ constructor(latitude: number, longitude: number, description?: string);
+ }
+
+ export interface Label {
+ /** Label name */
+ name: string;
+ /** Label ID */
+ id: string;
+ /** Color assigned to the label */
+ hexColor: string;
+
+ /** Get all chats that have been assigned this Label */
+ getChats: () => Promise;
+ }
+
+ /** Options for sending a message */
+ export interface MessageSendOptions {
+ /** Show links preview. Has no effect on multi-device accounts. */
+ linkPreview?: boolean;
+ /** Send audio as voice message */
+ sendAudioAsVoice?: boolean;
+ /** Send video as gif */
+ sendVideoAsGif?: boolean;
+ /** Send media as sticker */
+ sendMediaAsSticker?: boolean;
+ /** Send media as document */
+ sendMediaAsDocument?: boolean;
+ /** Automatically parse vCards and send them as contacts */
+ parseVCards?: boolean;
+ /** Image or videos caption */
+ caption?: string;
+ /** Id of the message that is being quoted (or replied to) */
+ quotedMessageId?: string;
+ /** Contacts that are being mentioned in the message */
+ mentions?: Contact[];
+ /** Send 'seen' status */
+ sendSeen?: boolean;
+ /** Media to be sent */
+ media?: MessageMedia;
+ /** Extra options */
+ extra?: any;
+ /** Sticker name, if sendMediaAsSticker is true */
+ stickerName?: string;
+ /** Sticker author, if sendMediaAsSticker is true */
+ stickerAuthor?: string;
+ /** Sticker categories, if sendMediaAsSticker is true */
+ stickerCategories?: string[];
+ }
+
+ export interface MediaFromURLOptions {
+ client?: Client;
+ filename?: string;
+ unsafeMime?: boolean;
+ reqOptions?: AxiosInterceptorOptions;
+ }
+
+ /** Media attached to a message */
+ export class MessageMedia {
+ /** MIME type of the attachment */
+ mimetype: string;
+ /** Base64-encoded data of the file */
+ data: string;
+ /** Document file name. Value can be null */
+ filename?: string | null;
+ /** Document file size in bytes. Value can be null. */
+ filesize?: number | null;
+
+ /**
+ * @param {string} mimetype MIME type of the attachment
+ * @param {string} data Base64-encoded data of the file
+ * @param {?string} filename Document file name. Value can be null
+ * @param {?number} filesize Document file size in bytes. Value can be null.
+ */
+ constructor(
+ mimetype: string,
+ data: string,
+ filename?: string | null,
+ filesize?: number | null
+ );
+
+ /** Creates a MessageMedia instance from a local file path */
+ static fromFilePath: (filePath: string) => MessageMedia;
+
+ /** Creates a MessageMedia instance from a URL */
+ static fromUrl: (
+ url: string,
+ options?: MediaFromURLOptions
+ ) => Promise;
+ }
+
+ export type MessageContent = |
+ string |
+ MessageMedia |
+ Location |
+ Contact |
+ Contact[] |
+ List |
+ Buttons;
+
+ /**
+ * Represents a Contact on WhatsApp
+ *
+ * @example
+ * {
+ * id: {
+ * server: 'c.us',
+ * user: '554199999999',
+ * _serialized: `554199999999@c.us`
+ * },
+ * number: '554199999999',
+ * isBusiness: false,
+ * isEnterprise: false,
+ * labels: [],
+ * name: undefined,
+ * pushname: 'John',
+ * sectionHeader: undefined,
+ * shortName: undefined,
+ * statusMute: false,
+ * type: 'in',
+ * verifiedLevel: undefined,
+ * verifiedName: undefined,
+ * isMe: false,
+ * isUser: true,
+ * isGroup: false,
+ * isWAContact: true,
+ * isMyContact: false
+ * }
+ */
+ export interface Contact {
+ /** Contact's phone number */
+ number: string;
+ /** Indicates if the contact is a business contact */
+ isBusiness: boolean;
+ /** ID that represents the contact */
+ id: ContactId;
+ /** Indicates if the contact is an enterprise contact */
+ isEnterprise: boolean;
+ /** Indicates if the contact is a group contact */
+ isGroup: boolean;
+ /** Indicates if the contact is the current user's contact */
+ isMe: boolean;
+ /** Indicates if the number is saved in the current phone's contacts */
+ isMyContact: boolean;
+ /** Indicates if the contact is a user contact */
+ isUser: boolean;
+ /** Indicates if the number is registered on WhatsApp */
+ isWAContact: boolean;
+ /** Indicates if you have blocked this contact */
+ isBlocked: boolean;
+ /** @todo verify labels type. didn't have any documentation */
+ labels?: string[];
+ /** The contact's name, as saved by the current user */
+ name?: string;
+ /** The name that the contact has configured to be shown publically */
+ pushname: string;
+ /** @todo missing documentation */
+ sectionHeader: string;
+ /** A shortened version of name */
+ shortName?: string;
+ /** Indicates if the status from the contact is muted */
+ statusMute: boolean;
+ /** @todo missing documentation */
+ type: string;
+ /** @todo missing documentation */
+ verifiedLevel?: undefined;
+ /** @todo missing documentation */
+ verifiedName?: undefined;
+
+ /** Returns the contact's profile picture URL, if privacy settings allow it */
+ getProfilePicUrl: () => Promise;
+
+ /** Returns the Chat that corresponds to this Contact.
+ * Will return null when getting chat for currently logged in user.
+ */
+ getChat: () => Promise;
+
+ /** Returns the contact's countrycode, (1541859685@c.us) => (1) */
+ getCountryCode(): Promise;
+
+ /** Returns the contact's formatted phone number, (12345678901@c.us) => (+1 (234) 5678-901) */
+ getFormattedNumber(): Promise;
+
+ /** Blocks this contact from WhatsApp */
+ block: () => Promise;
+
+ /** Unlocks this contact from WhatsApp */
+ unblock: () => Promise;
+
+ /** Gets the Contact's current "about" info. Returns null if you don't have permission to read their status. */
+ getAbout: () => Promise;
+
+ /** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */
+ getCommonGroups: () => Promise;
+ }
+
+ export interface ContactId {
+ server: string;
+ user: string;
+ _serialized: string;
+ }
+
+ export interface BusinessContact extends Contact {
+ /**
+ * The contact's business profile
+ * @todo add a more specific type for the object
+ */
+ businessProfile: object;
+ }
+
+ export interface PrivateContact extends Contact { }
+
+ /**
+ * Represents a Chat on WhatsApp
+ *
+ * @example
+ * {
+ * id: {
+ * server: 'c.us',
+ * user: '554199999999',
+ * _serialized: `554199999999@c.us`
+ * },
+ * name: '+55 41 9999-9999',
+ * isGroup: false,
+ * isReadOnly: false,
+ * unreadCount: 6,
+ * timestamp: 1591484087,
+ * archived: false
+ * }
+ */
+ export class Chat {
+ /** Indicates if the Chat is archived */
+ archived: boolean;
+ /** ID that represents the chat */
+ id: ChatId;
+ /** Indicates if the Chat is a Group Chat */
+ isGroup: boolean;
+ /** Indicates if the Chat is readonly */
+ isReadOnly: boolean;
+ /** Indicates if the Chat is muted */
+ isMuted: boolean;
+ /** Unix timestamp for when the mute expires */
+ muteExpiration: number;
+ /** Title of the chat */
+ name: string;
+ /** Unix timestamp for when the last activity occurred */
+ timestamp: number;
+ /** Amount of messages unread */
+ unreadCount: number;
+ /** Last message fo chat */
+ lastMessage: Message;
+
+ /** Archives this chat */
+ archive: () => Promise;
+ /** Pins this chat and returns its new Pin state */
+ pin: () => Promise;
+ /** Unpins this chat and returns its new Pin state */
+ unpin: () => Promise;
+ /** Clears all messages from the chat */
+ clearMessages: () => Promise;
+ /** Stops typing or recording in chat immediately. */
+ clearState: () => Promise;
+ /** Deletes the chat */
+ delete: () => Promise;
+ /** Loads chat messages, sorted from earliest to latest. */
+ fetchMessages: (searchOptions: MessageSearchOptions) => Promise;
+ /** Mutes this chat forever, unless a date is specified */
+ mute: (unmuteDate?: Date) => Promise;
+ /** Send a message to this chat */
+ sendMessage: (
+ content: MessageContent,
+ options?: MessageSendOptions
+ ) => Promise;
+ /** Set the message as seen */
+ sendSeen: () => Promise;
+ /** Simulate recording audio in chat. This will last for 25 seconds */
+ sendStateRecording: () => Promise;
+ /** Simulate typing in chat. This will last for 25 seconds. */
+ sendStateTyping: () => Promise;
+ /** un-archives this chat */
+ unarchive: () => Promise;
+ /** Unmutes this chat */
+ unmute: () => Promise;
+ /** Returns the Contact that corresponds to this Chat. */
+ getContact: () => Promise;
+ /** Marks this Chat as unread */
+ markUnread: () => Promise;
+ /** Returns array of all Labels assigned to this Chat */
+ getLabels: () => Promise;
+ /** Add or remove labels to this Chat */
+ changeLabels: (labelIds: Array) => Promise;
+ }
+
+ export interface MessageSearchOptions {
+ /**
+ * The amount of messages to return. If no limit is specified, the available messages will be returned.
+ * Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation.
+ * Set this to Infinity to load all messages.
+ */
+ limit?: number;
+ /**
+ * Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
+ */
+ fromMe?: boolean;
+ }
+
+ /**
+ * Id that represents the chat
+ *
+ * @example
+ * id: {
+ * server: 'c.us',
+ * user: '554199999999',
+ * _serialized: `554199999999@c.us`
+ * },
+ */
+ export interface ChatId {
+ /**
+ * Whatsapp server domain
+ * @example `c.us`
+ */
+ server: string;
+ /**
+ * User whatsapp number
+ * @example `554199999999`
+ */
+ user: string;
+ /**
+ * Serialized id
+ * @example `554199999999@c.us`
+ */
+ _serialized: string;
+ }
+
+ export interface PrivateChat extends Chat { }
+
+ export type GroupParticipant = {
+ id: ContactId;
+ isAdmin: boolean;
+ isSuperAdmin: boolean;
+ };
+
+ /** Promotes or demotes participants by IDs to regular users or admins */
+ export type ChangeParticipantsPermissions = (
+ participantIds: Array
+ ) => Promise<{
+ status: number
+ }>;
+
+ /** An object that handles the result of addParticipants method */
+ /** An object that handles the result for addParticipants method */
+ export interface AddParticipantsResult {
+ [participantId: string]: {
+ code: number;
+ message: string;
+ isInviteV4Sent: boolean,
+ };
+ };
+
+ /** An object that handles options for adding participants */
+ export interface AddParticipantsOptions {
+ /**
+ * The number of milliseconds to wait before adding the next participant.
+ * If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added
+ * (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100
+ * will be added). If sleep is a number, a sleep time equal to its value will be added
+ * @default [250,500]
+ */
+ sleep?: Array | number,
+ /**
+ * If true, the inviteV4 will be sent to those participants
+ * who have restricted others from being automatically added to groups,
+ * otherwise the inviteV4 won't be sent
+ * @default true
+ */
+ autoSendInviteV4?: boolean,
+ /**
+ * The comment to be added to an inviteV4 (empty string by default)
+ * @default ''
+ */
+ comment?: string
+ };
+
+ export interface GroupChat extends Chat {
+ /** Group owner */
+ owner: ContactId;
+ /** Date at which the group was created */
+ createdAt: Date;
+ /** Group description */
+ description: string;
+ /** Group participants */
+ participants: Array;
+ /** Adds a list of participants by ID to the group */
+ addParticipants: (participantIds: string | string[], options?: AddParticipantsOptions) => Promise | string>;
+ /** Removes a list of participants by ID to the group */
+ removeParticipants: (participantIds: string[]) => Promise<{ status: number }>;
+ /** Promotes participants by IDs to admins */
+ promoteParticipants: ChangeParticipantsPermissions;
+ /** Demotes participants by IDs to regular users */
+ demoteParticipants: ChangeParticipantsPermissions;
+ /** Updates the group subject */
+ setSubject: (subject: string) => Promise;
+ /** Updates the group description */
+ setDescription: (description: string) => Promise;
+ /** Updates the group settings to only allow admins to send messages
+ * @param {boolean} [adminsOnly=true] Enable or disable this option
+ * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
+ */
+ setMessagesAdminsOnly: (adminsOnly?: boolean) => Promise;
+ /**
+ * Updates the group settings to only allow admins to edit group info (title, description, photo).
+ * @param {boolean} [adminsOnly=true] Enable or disable this option
+ * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
+ */
+ setInfoAdminsOnly: (adminsOnly?: boolean) => Promise;
+ /** Gets the invite code for a specific group */
+ getInviteCode: () => Promise;
+ /** Invalidates the current group invite code and generates a new one */
+ revokeInvite: () => Promise;
+ /** Makes the bot leave the group */
+ leave: () => Promise;
+ /** Sets the group's picture.*/
+ setPicture: (media: MessageMedia) => Promise;
+ /** Deletes the group's picture */
+ deletePicture: () => Promise;
+ }
+
+ /**
+ * Represents the metadata associated with a given product
+ *
+ */
+ export interface ProductMetadata {
+ /** Product Id */
+ id: string;
+ /** Product Name */
+ name: string;
+ /** Product Description */
+ description: string;
+ /** Retailer ID */
+ retailer_id?: string;
+ }
+
+ /**
+ * Represents a Product on Whatsapp
+ * @example
+ * {
+ * "id": "123456789",
+ * "price": "150000",
+ * "thumbnailId": "123456789",
+ * "thumbnailUrl": "https://mmg.whatsapp.net",
+ * "currency": "GTQ",
+ * "name": "Store Name",
+ * "quantity": 1
+ * }
+ */
+ export interface Product {
+ /** Product Id */
+ id: string;
+ /** Price */
+ price?: string;
+ /** Product Thumbnail*/
+ thumbnailUrl: string;
+ /** Currency */
+ currency: string;
+ /** Product Name */
+ name: string;
+ /** Product Quantity*/
+ quantity: number;
+ /** Gets the Product metadata */
+ getData: () => Promise;
+ }
+
+ /**
+ * Represents a Order on WhatsApp
+ *
+ * @example
+ * {
+ * "products": [
+ * {
+ * "id": "123456789",
+ * "price": "150000",
+ * "thumbnailId": "123456789",
+ * "thumbnailUrl": "https://mmg.whatsapp.net",
+ * "currency": "GTQ",
+ * "name": "Store Name",
+ * "quantity": 1
+ * }
+ * ],
+ * "subtotal": "150000",
+ * "total": "150000",
+ * "currency": "GTQ",
+ * "createdAt": 1610136796,
+ * "sellerJid": "55555555@s.whatsapp.net"
+ * }
+ */
+ export interface Order {
+ /** List of products*/
+ products: Array;
+ /** Order Subtotal */
+ subtotal: string;
+ /** Order Total */
+ total: string;
+ /** Order Currency */
+ currency: string;
+ /** Order Created At*/
+ createdAt: number;
+ }
+
+ /**
+ * Represents a Payment on WhatsApp
+ *
+ * @example
+ * {
+ * id: {
+ * fromMe: true,
+ * remote: {
+ * server: 'c.us',
+ * user: '5511999999999',
+ * _serialized: '5511999999999@c.us'
+ * },
+ * id: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
+ * _serialized: 'true_5511999999999@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ * },
+ * paymentCurrency: 'BRL',
+ * paymentAmount1000: 1000,
+ * paymentMessageReceiverJid: {
+ * server: 'c.us',
+ * user: '5511999999999',
+ * _serialized: '5511999999999@c.us'
+ * },
+ * paymentTransactionTimestamp: 1623463058,
+ * paymentStatus: 4,
+ * paymentTxnStatus: 4,
+ * paymentNote: 'note'
+ * }
+ */
+ export interface Payment {
+ /** Payment Id*/
+ id: object;
+ /** Payment currency */
+ paymentCurrency: string;
+ /** Payment ammount */
+ paymentAmount1000: number;
+ /** Payment receiver */
+ paymentMessageReceiverJid: object;
+ /** Payment transaction timestamp */
+ paymentTransactionTimestamp: number;
+ /** Payment paymentStatus */
+ paymentStatus: number;
+ /** Integer that represents the payment Text */
+ paymentTxnStatus: number;
+ /** The note sent with the payment */
+ paymentNote: string;
+ }
+
+ /**
+ * Represents a Call on WhatsApp
+ *
+ * @example
+ * Call {
+ * id: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
+ * from: '5511999999@c.us',
+ * timestamp: 1625003709,
+ * isVideo: false,
+ * isGroup: false,
+ * fromMe: false,
+ * canHandleLocally: false,
+ * webClientShouldHandle: false,
+ * participants: []
+ * }
+ */
+ export interface Call {
+ /** Call Id */
+ id: string;
+ /** from */
+ from?: string;
+ /** Unix timestamp for when the call was created*/
+ timestamp: number;
+ /** Is video */
+ isVideo: boolean;
+ /** Is Group */
+ isGroup: boolean;
+ /** Indicates if the call was sent by the current user */
+ fromMe: boolean;
+ /** indicates if the call can be handled in waweb */
+ canHandleLocally: boolean;
+ /** indicates if the call should be handled in waweb */
+ webClientShouldHandle: boolean;
+ /** Object with participants */
+ participants: object;
+
+ /** Reject the call */
+ reject: () => Promise;
+ }
+
+ /** Message type List */
+ export class List {
+ body: string;
+ buttonText: string;
+ sections: Array;
+ title?: string | null;
+ footer?: string | null;
+
+ constructor(
+ body: string,
+ buttonText: string,
+ sections: Array,
+ title?: string | null,
+ footer?: string | null
+ );
+ }
+
+ /** Message type Buttons */
+ export class Buttons {
+ body: string | MessageMedia;
+ buttons: Array<{
+ buttonId: string;
+ buttonText: {
+ displayText: string
+ };
+ type: number;
+ }>;
+ title?: string | null;
+ footer?: string | null;
+
+ constructor(
+ body: string,
+ buttons: Array<{
+ id?: string; body: string
+ }>,
+ title?: string | null,
+ footer?: string | null
+ );
+ }
+
+ /** Message type Reaction */
+ export class Reaction {
+ id: MessageId;
+ orphan: number;
+ orphanReason?: string;
+ timestamp: number;
+ reaction: string;
+ read: boolean;
+ msgId: MessageId;
+ senderId: string;
+ ack?: number;
+ }
+
+ export class PollVote {
+ selectedOptions: string[];
+ sender: string;
+ senderTimestampMs: number;
+ }
+
+ export type ReactionList = {
+ id: string;
+ aggregateEmoji: string;
+ hasReactionByMe: boolean;
+ senders: Array;
+ };
+
+ export interface CallOptions {
+ isGroup: boolean;
+ }
}
interface LinkWithQR {
- qr: {
- maxRetries: number;
- };
- phone ? : never;
+ qr: {
+ maxRetries: number;
+ };
+ phone?: never;
}
interface LinkWithPhoneNumber {
- qr ? : never;
- phone: {
- number: string;
- };
+ qr?: never;
+ phone: {
+ number: string;
+ };
}
export class LinkingMethod {
- qr: {
- maxRetries: number;
- };
- phone: {
- number: string;
- };
- isQR: () => boolean;
- isPhone: () => boolean;
- constructor({
- qr,
- phone
- }: LinkWithQR | LinkWithPhoneNumber);
+ qr: {
+ maxRetries: number;
+ };
+ phone: {
+ number: string;
+ };
+ isQR: () => boolean;
+ isPhone: () => boolean;
+ constructor({
+ qr,
+ phone
+ }: LinkWithQR | LinkWithPhoneNumber);
}
export = WAWebJS;
\ No newline at end of file
diff --git a/index.html b/index.html
deleted file mode 100644
index c4d6591..0000000
--- a/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Docs coming soon
diff --git a/index.js b/index.js
index 46aad02..adcbb25 100644
--- a/index.js
+++ b/index.js
@@ -9,32 +9,34 @@
'use strict';
-export { default as Client } from './src/Client.js';
+const Constants = require('./src/util/Constants');
-// Structures
-export {
- Chat,
- PrivateChat,
- GroupChat,
- Message,
- MessageMedia,
- Contact,
- PrivateContact,
- BusinessContact,
- ClientInfo,
- Location,
- ProductMetadata,
- List,
- Buttons,
- PollVote,
- Call,
- LinkingMethod
-} from './src/structures/index.js';
+module.exports = {
+ Client: require('./src/Client'),
-// Auth Strategies
-export { default as NoAuth } from './src/authStrategies/NoAuth.js'
-export { default as LocalAuth } from './src/authStrategies/LocalAuth.js'
-export { default as RemoteAuth } from './src/authStrategies/RemoteAuth.js'
-export { default as LegacySessionAuth } from './src/authStrategies/LegacySessionAuth.js'
+ version: require('./package.json').version,
-export * from './src/util/Constants.js';
\ No newline at end of file
+ // Structures
+ Chat: require('./src/structures/Chat'),
+ PrivateChat: require('./src/structures/PrivateChat'),
+ GroupChat: require('./src/structures/GroupChat'),
+ Message: require('./src/structures/Message'),
+ MessageMedia: require('./src/structures/MessageMedia'),
+ Contact: require('./src/structures/Contact'),
+ PrivateContact: require('./src/structures/PrivateContact'),
+ BusinessContact: require('./src/structures/BusinessContact'),
+ ClientInfo: require('./src/structures/ClientInfo'),
+ Location: require('./src/structures/Location'),
+ ProductMetadata: require('./src/structures/ProductMetadata'),
+ List: require('./src/structures/List'),
+ Buttons: require('./src/structures/Buttons'),
+ PollVote: require('./src/structures/PollVote'),
+
+ // Auth Strategies
+ LinkingMethod: require('./src/LinkingMethod'),
+ NoAuth: require('./src/authStrategies/NoAuth'),
+ LocalAuth: require('./src/authStrategies/LocalAuth'),
+ RemoteAuth: require('./src/authStrategies/RemoteAuth'),
+ LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'),
+ ...Constants
+};
diff --git a/package.json b/package.json
index 086b123..c4c22fe 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,14 @@
{
"name": "mywajs",
- "version": "2.0.7",
+ "version": "2.0.8",
"description": "remake wwebjs using playwright and wajs",
"main": "./index.js",
"typings": "./index.d.ts",
- "type": "module",
"scripts": {
"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"
+ "generate-docs": "jsdoc --configure .jsdoc.json --verbose"
},
"repository": {
"type": "git",
@@ -33,21 +32,23 @@
},
"homepage": "https://github.com/amiruldev20",
"dependencies": {
- "@pedroslopez/moduleraid": "^5.0.2",
- "@wppconnect/wa-js": "github:wppconnect-team/wa-js",
+ "@amiruldev/moduleraid": "github:amiruldev20/moduleraid",
+ "@amiruldev/wajs": "github:amiruldev20/wajs",
"axios": "^1.3.6",
"chalk": "^5.2.0",
"cheerio": "^1.0.0-rc.12",
"fluent-ffmpeg": "^2.1.2",
- "file-type": "^18.2.1",
+ "file-type": "^16.5.3",
"link-preview-js": "^3.0.4",
"mime-types": "^2.1.35",
"mime": "latest",
"node-webpmux": "^3.1.0",
+ "node-fetch": "^2.6.7",
"os": "^0.1.2",
"path": "^0.12.7",
- "playwright-chromium": "^1.37.0",
- "qrcode-terminal": "0.12.0"
+ "playwright-chromium": "latest",
+ "qrcode-terminal": "0.12.0",
+ "sharp": "^0.32.4"
},
"devDependencies": {
"@types/node-fetch": "^2.5.12",
@@ -68,5 +69,8 @@
"archiver": "^5.3.1",
"fs-extra": "^10.1.0",
"unzipper": "^0.10.11"
+ },
+ "directories": {
+ "example": "example"
}
-}
+}
\ No newline at end of file
diff --git a/src/Client.js b/src/Client.js
index 7ca6540..290a99c 100644
--- a/src/Client.js
+++ b/src/Client.js
@@ -6,2674 +6,2541 @@
* wa: 085157489446
* ig: amirul.dev
*/
-"use strict";
-
-import EventEmitter from "events";
-import playwright from "playwright-chromium";
-import moduleRaid from "@pedroslopez/moduleraid/moduleraid.js";
-import { createRequire } from "module";
-import chalk from "chalk";
-import { promises as fs } from "fs";
-import { exec } from "child_process";
-import Fs from "fs";
-import path from "path";
-
-import Util from "./util/Util.js";
-import InterfaceController from "./util/InterfaceController.js";
-import {
- WhatsWebURL,
- DefaultOptions,
- Events,
- WAState,
-} from "./util/Constants.js";
-import { ExposeStore, LoadUtils } from "./util/Injected.js";
-import ChatFactory from "./factories/ChatFactory.js";
-import ContactFactory from "./factories/ContactFactory.js";
-import {
- PollVote,
- ClientInfo,
- Message,
- MessageMedia,
- Contact,
- Location,
- GroupNotification,
- Label,
- Call,
- Buttons,
- List,
- Reaction,
- LinkingMethod,
-} from "./structures/index.js";
-import LegacySessionAuth from "./authStrategies/LegacySessionAuth.js";
-import NoAuth from "./authStrategies/NoAuth.js";
-
-const require = createRequire(import.meta.url);
-
+'use strict';
+const fs = require('fs')
+const Fs = require('fs')
+const path = require('path')
+
+const EventEmitter = require('events');
+const playwright = require('playwright-chromium');
+const moduleRaid = require('@amiruldev/moduleraid/moduleraid');
+const Util = require('./util/Util');
+const InterfaceController = require('./util/InterfaceController');
+const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants');
+const { ExposeStore, LoadUtils } = require('./util/Injected');
+const ChatFactory = require('./factories/ChatFactory');
+const ContactFactory = require('./factories/ContactFactory');
+const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List, Reaction, Chat, PollVote } = require('./structures');
+const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
+const NoAuth = require('./authStrategies/NoAuth');
+const LinkingMethod = require('./LinkingMethod')
+
+/**
+ * Starting point for interacting with the WhatsApp Web API
+ * @extends {EventEmitter}
+ * @param {object} options - Client options
+ * @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used.
+ * @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
+ * @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
+ * @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up
+ * @param {string} options.restartOnAuthFail- @deprecated This option should be set directly on the LegacySessionAuth.
+ * @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
+ * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
+ * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
+ * @param {string} options.userAgent - User agent to use in puppeteer
+ * @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers
+ * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy.
+ *
+ * @fires Client#qr
+ * @fires Client#authenticated
+ * @fires Client#auth_failure
+ * @fires Client#ready
+ * @fires Client#message
+ * @fires Client#message_ack
+ * @fires Client#message_create
+ * @fires Client#message_revoke_me
+ * @fires Client#message_revoke_everyone
+ * @fires Client#media_uploaded
+ * @fires Client#group_join
+ * @fires Client#group_leave
+ * @fires Client#group_update
+ * @fires Client#disconnected
+ * @fires Client#change_state
+ * @fires Client#contact_changed
+ * @fires Client#group_admin_changed
+ */
class Client extends EventEmitter {
- constructor(options = {}) {
- super();
-
- this.options = Util.mergeDefault(DefaultOptions, options);
-
- /* login phone method */
- if (!this.options.linkingMethod) {
- this.options.linkingMethod = new LinkingMethod({
- qr: {
- maxRetries: this.options.qrMaxRetries,
- },
- });
- }
-
- if (!this.options.authStrategy) {
- if (Object.prototype.hasOwnProperty.call(this.options, "session")) {
- process.emitWarning(
- "options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. " +
- "Use the LocalAuth authStrategy, don't pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).",
- "DeprecationWarning"
+ constructor(options = {}) {
+ super();
+
+ this.options = Util.mergeDefault(DefaultOptions, options);
+
+ if (!this.options.linkingMethod) {
+ this.options.linkingMethod = new LinkingMethod({
+ qr: {
+ maxRetries: this.options.qrMaxRetries,
+ },
+ });
+ }
+
+ if (!this.options.authStrategy) {
+ if (Object.prototype.hasOwnProperty.call(this.options, "session")) {
+ process.emitWarning(
+ "options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. " +
+ "Use the LocalAuth authStrategy, don't pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).",
+ "DeprecationWarning"
+ );
+
+ this.authStrategy = new LegacySessionAuth({
+ session: this.options.session,
+ restartOnAuthFail: this.options.restartOnAuthFail,
+ });
+ } else {
+ this.authStrategy = new NoAuth();
+ }
+ } else {
+ this.authStrategy = this.options.authStrategy;
+ }
+
+ this.authStrategy.setup(this);
+
+ this.pupBrowser = null;
+ this.mPage = null;
+
+ Util.setFfmpegPath(this.options.ffmpegPath);
+ }
+
+ /**
+ * Sets up events and requirements, kicks off authentication request
+ */
+ async initialize() {
+ let [browser, context, page] = [null, null];
+
+ await this.authStrategy.beforeBrowserInitialized();
+
+ const playwrightOpts = this.options.playwright;
+ if (playwrightOpts && playwrightOpts.wsEndpoint) {
+ browser = await playwright.chromium.connect(playwrightOpts.wsEndpoint, {
+ timeout: 0,
+ ...playwrightOpts,
+ });
+ page = await context.newPage();
+ } else {
+ const browserArgs = [...(playwrightOpts.args || [])];
+ if (!browserArgs.find((arg) => arg.includes("--user-agent"))) {
+ browserArgs.push(`--user-agent=${this.options.userAgent}`);
+ }
+
+ browser = await playwright.chromium.launchPersistentContext('.mywa_auth', {
+ ...playwrightOpts,
+ args: browserArgs,
+ timeout: 0,
+ }
+ );
+ page = (await browser.pages())[0];
+ }
+
+ if (this.options.userAgent) {
+ await page.setExtraHTTPHeaders({
+ "User-Agent": this.options.userAgent,
+ });
+ }
+
+ this.pupBrowser = browser;
+ this.mPage = page;
+
+ await this.authStrategy.afterBrowserInitialized();
+
+ await page.goto(WhatsWebURL, {
+ waitUntil: 'load',
+ timeout: 0,
+ referer: 'https://whatsapp.com/'
+ });
+
+ await page.addScriptTag({
+ path: require.resolve("@amiruldev/wajs"),
+ });
+
+ await page.waitForFunction(() => window.WPP ?.isReady, {
+ timeout: 60000
+ });
+ await page
+ .evaluate(
+ ({
+ markOnlineAvailable,
+ isBeta
+ }) => {
+ WPP.chat.defaultSendMessageOptions.createChat = true;
+ if (markOnlineAvailable) WPP.conn.setKeepAlive(markOnlineAvailable);
+ if (isBeta) WPP.conn.joinWebBeta(true);
+ }, {
+ markOnlineAvailable: this.options.markOnlineAvailable,
+ isBeta: this.options.isBeta,
+ }
+ )
+ .catch(() => false);
+
+ await page.evaluate(() => {
+ WPP.conn.setLimit('maxMediaSize', 16777216)
+ WPP.conn.setLimit('maxFileSize', 104857600)
+ WPP.conn.setLimit('maxShare', 100)
+ WPP.conn.setLimit('statusVideoMaxDuration', 120)
+ WPP.conn.setLimit('unlimitedPin', true);
+ })
+
+ const inject = async () => {
+ await page.evaluate(ExposeStore, moduleRaid.toString()).catch(async error => {
+ // These error, not as a result of injection directly, but since we use moduleRaid. nothing to do about this but do it again till it works
+ if (error.message.includes('EmojiUtil') || error.message.includes('Prism') || error.message.includes('createOrUpdateReactions')) {
+ await inject();
+ }
+ });
+ };
+ await inject();
+
+ // new
+ const getElementByXpath = (path) => {
+ return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+ };
+
+ let lastPercent = null,
+ lastPercentMessage = null;
+ let loads = false
+ // Menambahkan fungsi `loadingScreen` ke halaman
+ await page.exposeFunction('loadingScreen', (percent, message) => {
+ console.log('Process: ' + percent + '%');
+ console.log('Message: ' + message);
+ });
+
+ const progressClass = 'progress.ZJWuG';
+
+ await page.exposeFunction('getProgressValue', async (progressClass) => {
+ const progressBar = await page.$(progressClass);
+ if (progressBar) {
+ return await progressBar.getAttribute('value');
+ }
+ return null;
+ });
+
+ await page.exposeFunction('getProgressMessage', async () => {
+ const progressMessage = await page.$('div._3HbCE')
+ if (progressMessage) {
+ return progressMessage.textContent;
+ }
+ return null;
+ });
+
+ await page.evaluate(() => {
+ const observer = new MutationObserver(async () => {
+ const percent = await window.getProgressValue('progress.ZJWuG');
+ const message = await window.getProgressMessage();
+ if (percent !== null && message !== null) {
+ window.loadingScreen(percent, message);
+ }
+ });
+
+ observer.observe(document, {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ subtree: true,
+ });
+ });
+ console.log(
+ `You Used Selector: ${this.options.selector ? this.options.selector : "default"
+ }`
);
+ const INTRO_IMG_SELECTOR =
+ this.options.selector == 1
+ ? "div[role='textbox']"
+ : this.options.selector == 2
+ ? '[data-icon="chat"],[data-icon="intro-md-beta-logo-dark"],[data-icon="intro-md-beta-logo-light"]'
+ : this.options.selector == 3
+ ? "[data-icon='chat']"
+ : this.options.selector == 4
+ ? `['[data-icon*=community]', '[data-testid="intro-md-beta-logo-dark"], [data-testid="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"], [data-icon="intro-md-beta-logo-dark"], [data-icon="intro-md-beta-logo-light"]`
+ : "[data-icon='search']";
+
+ const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas';
+
+ // Checks which selector appears first
+ const needAuthentication = await Promise.race([
+ new Promise(resolve => {
+ page.waitForSelector(INTRO_IMG_SELECTOR, {
+ timeout: this.options.authTimeoutMs
+ })
+ .then(() => resolve(false))
+ .catch((err) => resolve(err));
+ }),
+ new Promise(resolve => {
+ page.waitForSelector(INTRO_QRCODE_SELECTOR, {
+ timeout: this.options.authTimeoutMs
+ })
+ .then(() => resolve(true))
+ .catch((err) => resolve(err));
+ })
+ ]);
+
+ // Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race;
+ if (needAuthentication instanceof Error) throw needAuthentication;
+
+ // Scan-qrcode selector was found. Needs authentication
+ if (needAuthentication) {
+ const {
+ failed,
+ failureEventPayload,
+ restart
+ } = await this.authStrategy.onAuthenticationNeeded();
+ if (failed) {
+ /**
+ * Emitted when there has been an error while trying to restore an existing session
+ * @event Client#auth_failure
+ * @param {string} message
+ */
+ this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
+ await this.destroy();
+ if (restart) {
+ // session restore failed so try again but without session to force new authentication
+ return this.initialize();
+ }
+ return;
+ }
+
+ const handleLinkWithQRCode = async () => {
+ const QR_CONTAINER = 'div[data-ref]';
+ const QR_RETRY_BUTTON = 'div[data-ref] > span > button';
+ let qrRetries = 0;
+ await page.exposeFunction('qrChanged', async (qr) => {
+ /**
+ * Emitted when a QR code is received
+ * @event Client#qr
+ * @param {string} qr QR Code
+ */
+ this.emit(Events.QR_RECEIVED, qr);
+ if (this.options.linkingMethod.qr.maxRetries > 0) {
+ qrRetries++;
+ if (qrRetries > this.options.linkingMethod.qr.maxRetries) {
+ this.emit(
+ Events.DISCONNECTED,
+ 'Max qrcode retries reached'
+ );
+ await this.destroy();
+ }
+ }
+ });
+
+ await page.evaluate(
+ function (selectors) {
+ const qr_container = document.querySelector(
+ selectors.QR_CONTAINER
+ );
+ window.qrChanged(qr_container.dataset.ref);
+
+ const obs = new MutationObserver((muts) => {
+ muts.forEach((mut) => {
+ // Listens to qr token change
+ if (
+ mut.type === 'attributes' &&
+ mut.attributeName === 'data-ref'
+ ) {
+ window.qrChanged(mut.target.dataset.ref);
+ }
+ // Listens to retry button, when found, click it
+ else if (mut.type === 'childList') {
+ const retry_button = document.querySelector(
+ selectors.QR_RETRY_BUTTON
+ );
+ if (retry_button) retry_button.click();
+ }
+ });
+ });
+ obs.observe(qr_container.parentElement, {
+ subtree: true,
+ childList: true,
+ attributes: true,
+ attributeFilter: ['data-ref'],
+ });
+ }, {
+ QR_CONTAINER,
+ QR_RETRY_BUTTON,
+ }
+ )
+ }
+
+ const handleLinkWithPhoneNumber = async () => {
+ const LINK_WITH_PHONE_BUTTON = 'div._3rDmx div._2rQUO span._3iLTh';
+ const PHONE_NUMBER_INPUT = 'input.selectable-text';
+ const NEXT_BUTTON = 'div._1M6AF._3QJHf';
+ const CODE_CONTAINER = '[aria-details="link-device-phone-number-code-screen-instructions"]';
+ const GENERATE_NEW_CODE_BUTTON = '[data-testid="popup-controls-ok"]';
+ const LINK_WITH_PHONE_VIEW = 'div._1x9Rv._3qC8O';
+
+ await page.exposeFunction('codeChanged', async (code) => {
+ /**
+ * Emitted when a QR code is received
+ * @event Client#code
+ * @param {string} code Code
+ */
+ this.emit(Events.CODE_RECEIVED, code);
+ });
+ const clickOnLinkWithPhoneButton = async () => {
+ await page.waitForSelector(LINK_WITH_PHONE_BUTTON, {
+ timeout: 0
+ });
+ await page.click(LINK_WITH_PHONE_BUTTON);
+ };
+
+ const typePhoneNumber = async () => {
+ await page.waitForSelector(PHONE_NUMBER_INPUT);
+ const inputValue = await page.$eval(PHONE_NUMBER_INPUT, el => el.value);
+ await page.click(PHONE_NUMBER_INPUT);
+ for (let i = 0; i < inputValue.length; i++) {
+ await page.keyboard.press('Backspace');
+ }
+ await page.type(PHONE_NUMBER_INPUT, this.options.linkingMethod.phone.number);
+ };
+
+ await clickOnLinkWithPhoneButton();
+ await typePhoneNumber();
+ await page.click(NEXT_BUTTON);
+
+ await page.evaluate(async function (selectors) {
+ function waitForElementToExist(selector, timeout = 60000) {
+ return new Promise((resolve, reject) => {
+ if (document.querySelector(selector)) {
+ return resolve(document.querySelector(selector));
+ }
+
+ const observer = new MutationObserver(() => {
+ if (document.querySelector(selector)) {
+ resolve(document.querySelector(selector));
+ observer.disconnect();
+ }
+ });
+
+ observer.observe(document.body, {
+ subtree: true,
+ childList: true,
+ });
+
+ if (timeout > 0) {
+ setTimeout(() => {
+ reject(
+ new Error(
+ `waitForElementToExist: ${selector} not found in time`
+ )
+ );
+ }, timeout);
+ }
+ });
+ }
+
+ await waitForElementToExist(selectors.CODE_CONTAINER);
+
+ const getCode = () => {
+ const codeContainer = document.querySelector(selectors.CODE_CONTAINER);
+ const code = Array.from(codeContainer.children)[0];
+
+ const cells = Array.from(code.children);
+ return cells.map((cell) => cell.textContent).join('');
+ };
+ let code = getCode();
+ window.codeChanged(code);
+
+ const entirePageObserver = new MutationObserver(() => {
+ const generateNewCodeButton = document.querySelector(selectors.GENERATE_NEW_CODE_BUTTON);
+ if (generateNewCodeButton) {
+ generateNewCodeButton.click();
+ return;
+ }
+ });
+ entirePageObserver.observe(document, {
+ subtree: true,
+ childList: true,
+ });
+
+ const linkWithPhoneView = document.querySelector(selectors.LINK_WITH_PHONE_VIEW);
+ const linkWithPhoneViewObserver = new MutationObserver(() => {
+ const newCode = getCode();
+ if (newCode !== code) {
+ window.codeChanged(newCode);
+ code = newCode;
+ }
+ });
+ linkWithPhoneViewObserver.observe(linkWithPhoneView, {
+ subtree: true,
+ childList: true,
+ });
+
+ }, {
+ CODE_CONTAINER,
+ GENERATE_NEW_CODE_BUTTON,
+ LINK_WITH_PHONE_VIEW
+ });
+ };
+
+ const {
+ linkingMethod
+ } = this.options;
+
+ if (linkingMethod.isQR()) {
+ await handleLinkWithQRCode();
+ } else {
+ await handleLinkWithPhoneNumber();
+ }
+
+ // Wait for code scan
+ try {
+ await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 });
+ } catch (error) {
+ if (
+ error.name === 'ProtocolError' &&
+ error.message &&
+ error.message.match(/Target closed/)
+ ) {
+ // something has called .destroy() while waiting
+ return;
+ }
+
+ throw error;
+ }
+
+ }
+
+ await page.evaluate(() => {
+ /**
+ * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version.
+ * @param {string} lOperand The left operand for the WWeb version string to compare with
+ * @param {string} operator The comparison operator
+ * @param {string} rOperand The right operand for the WWeb version string to compare with
+ * @returns {boolean} Boolean value that indicates the result of the comparison
+ */
+ window.compareWwebVersions = (lOperand, operator, rOperand) => {
+ if (!['>', '>=', '<', '<=', '='].includes(operator)) {
+ throw new class _ extends Error {
+ constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; }
+ }('Invalid comparison operator is provided');
+
+ }
+ if (typeof lOperand !== 'string' || typeof rOperand !== 'string') {
+ throw new class _ extends Error {
+ constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; }
+ }('A non-string WWeb version type is provided');
+ }
+
+ lOperand = lOperand.replace(/-beta$/, '');
+ rOperand = rOperand.replace(/-beta$/, '');
+
+ while (lOperand.length !== rOperand.length) {
+ lOperand.length > rOperand.length
+ ? rOperand = rOperand.concat('0')
+ : lOperand = lOperand.concat('0');
+ }
+
+ lOperand = Number(lOperand.replace(/\./g, ''));
+ rOperand = Number(rOperand.replace(/\./g, ''));
+
+ return (
+ operator === '>' ? lOperand > rOperand :
+ operator === '>=' ? lOperand >= rOperand :
+ operator === '<' ? lOperand < rOperand :
+ operator === '<=' ? lOperand <= rOperand :
+ operator === '=' ? lOperand === rOperand :
+ false
+ );
+ };
+ });
+
+
+ const authEventPayload = await this.authStrategy.getAuthEventPayload();
+
+ /**
+ * Emitted when authentication is successful
+ * @event Client#authenticated
+ */
+ this.emit(Events.AUTHENTICATED, authEventPayload);
+
+ // Check window.Store Injection
+ await page.waitForFunction('window.Store != undefined');
+
+ await page.evaluate(async () => {
+ // safely unregister service workers
+ const registrations = await navigator.serviceWorker.getRegistrations();
+ for (let registration of registrations) {
+ registration.unregister();
+ }
+ });
+
+ //Load util functions (serializers, helper functions)
+ await page.evaluate(LoadUtils);
+
+ // Expose client info
+ /**
+ * Current connection information
+ * @type {ClientInfo}
+ */
+ this.info = new ClientInfo(this, await page.evaluate(() => {
+ return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
+ }));
+
+ // Add InterfaceController
+ this.interface = new InterfaceController(this);
+
+ // Register events
+ await page.exposeFunction('onAddMessageEvent', msg => {
+ if (msg.type === 'gp2') {
+ const notification = new GroupNotification(this, msg);
+ if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
+ /**
+ * Emitted when a user joins the chat via invite link or is added by an admin.
+ * @event Client#group_join
+ * @param {GroupNotification} notification GroupNotification with more information about the action
+ */
+ this.emit(Events.GROUP_JOIN, notification);
+ } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
+ /**
+ * Emitted when a user leaves the chat or is removed by an admin.
+ * @event Client#group_leave
+ * @param {GroupNotification} notification GroupNotification with more information about the action
+ */
+ this.emit(Events.GROUP_LEAVE, notification);
+ } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
+ /**
+ * Emitted when a current user is promoted to an admin or demoted to a regular user.
+ * @event Client#group_admin_changed
+ * @param {GroupNotification} notification GroupNotification with more information about the action
+ */
+ this.emit(Events.GROUP_ADMIN_CHANGED, notification);
+ } else if (msg.subtype === 'created_membership_requests') {
+ /**
+ * Emitted when some user requested to join the group
+ * that has the membership approval mode turned on
+ * @event Client#group_membership_request
+ * @param {GroupNotification} notification GroupNotification with more information about the action
+ * @param {string} notification.chatId The group ID the request was made for
+ * @param {string} notification.author The user ID that made a request
+ * @param {number} notification.timestamp The timestamp the request was made at
+ */
+ this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
+ } else {
+ /**
+ * Emitted when group settings are updated, such as subject, description or picture.
+ * @event Client#group_update
+ * @param {GroupNotification} notification GroupNotification with more information about the action
+ */
+ this.emit(Events.GROUP_UPDATE, notification);
+ }
+ return;
+ }
+
+ const message = new Message(this, msg);
+
+ /**
+ * Emitted when a new message is created, which may include the current user's own messages.
+ * @event Client#message_create
+ * @param {Message} message The message that was created
+ */
+ this.emit(Events.MESSAGE_CREATE, message);
+
+ if (msg.id.fromMe) return;
+
+ /**
+ * Emitted when a new message is received.
+ * @event Client#message
+ * @param {Message} message The message that was received
+ */
+ this.emit(Events.MESSAGE_RECEIVED, message);
+ });
+
+ let last_message;
+
+ await page.exposeFunction('onChangeMessageTypeEvent', (msg) => {
+
+ if (msg.type === 'revoked') {
+ const message = new Message(this, msg);
+ let revoked_msg;
+ if (last_message && msg.id.id === last_message.id.id) {
+ revoked_msg = new Message(this, last_message);
+ }
+
+ /**
+ * Emitted when a message is deleted for everyone in the chat.
+ * @event Client#message_revoke_everyone
+ * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
+ * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
+ * Note that due to the way this data is captured, it may be possible that this param will be undefined.
+ */
+ this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
+ }
+
+ });
+
+ await page.exposeFunction('onChangeMessageEvent', (msg) => {
+
+ if (msg.type !== 'revoked') {
+ last_message = msg;
+ }
+
+ /**
+ * The event notification that is received when one of
+ * the group participants changes their phone number.
+ */
+ const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
+
+ /**
+ * The event notification that is received when one of
+ * the contacts changes their phone number.
+ */
+ const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
+
+ if (isParticipant || isContact) {
+ /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
+ const message = new Message(this, msg);
+
+ const newId = isParticipant ? msg.recipients[0] : msg.to;
+ const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
+
+ /**
+ * Emitted when a contact or a group participant changes their phone number.
+ * @event Client#contact_changed
+ * @param {Message} message Message with more information about the event.
+ * @param {String} oldId The user's id (an old one) who changed their phone number
+ * and who triggered the notification.
+ * @param {String} newId The user's new id after the change.
+ * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
+ */
+ this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
+ }
+ });
+
+ await page.exposeFunction('onRemoveMessageEvent', (msg) => {
+
+ if (!msg.isNewMsg) return;
+
+ const message = new Message(this, msg);
+
+ /**
+ * Emitted when a message is deleted by the current user.
+ * @event Client#message_revoke_me
+ * @param {Message} message The message that was revoked
+ */
+ this.emit(Events.MESSAGE_REVOKED_ME, message);
+
+ });
+
+ await page.exposeFunction('onMessageAckEvent', (msg, ack) => {
+
+ const message = new Message(this, msg);
+
+ /**
+ * Emitted when an ack event occurrs on message type.
+ * @event Client#message_ack
+ * @param {Message} message The message that was affected
+ * @param {MessageAck} ack The new ACK value
+ */
+ this.emit(Events.MESSAGE_ACK, message, ack);
+
+ });
+
+ await page.exposeFunction('onChatUnreadCountEvent', async (data) => {
+ const chat = await this.getChatById(data.id);
+
+ /**
+ * Emitted when the chat unread count changes
+ */
+ this.emit(Events.UNREAD_COUNT, chat);
+ });
+
+ await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
+
+ const message = new Message(this, msg);
+
+ /**
+ * Emitted when media has been uploaded for a message sent by the client.
+ * @event Client#media_uploaded
+ * @param {Message} message The message with media that was uploaded
+ */
+ this.emit(Events.MEDIA_UPLOADED, message);
+ });
+
+ await page.exposeFunction('onAppStateChangedEvent', async (state) => {
+
+ /**
+ * Emitted when the connection state changes
+ * @event Client#change_state
+ * @param {WAState} state the new connection state
+ */
+ this.emit(Events.STATE_CHANGED, state);
+
+ const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
+
+ if (this.options.takeoverOnConflict) {
+ ACCEPTED_STATES.push(WAState.CONFLICT);
+
+ if (state === WAState.CONFLICT) {
+ setTimeout(() => {
+ this.mPage.evaluate(() => window.Store.AppState.takeover());
+ }, this.options.takeoverTimeoutMs);
+ }
+ }
+
+ if (!ACCEPTED_STATES.includes(state)) {
+ /**
+ * Emitted when the client has been disconnected
+ * @event Client#disconnected
+ * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect
+ */
+ await this.authStrategy.disconnect();
+ this.emit(Events.DISCONNECTED, state);
+ this.destroy();
+ }
+ });
+
+ await page.exposeFunction('onBatteryStateChangedEvent', (state) => {
+ const { battery, plugged } = state;
+
+ if (battery === undefined) return;
+
+ /**
+ * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
+ * @event Client#change_battery
+ * @param {object} batteryInfo
+ * @param {number} batteryInfo.battery - The current battery percentage
+ * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
+ * @deprecated
+ */
+ this.emit(Events.BATTERY_CHANGED, { battery, plugged });
+ });
+
+ await page.exposeFunction('onIncomingCall', (call) => {
+ /**
+ * Emitted when a call is received
+ * @event Client#incoming_call
+ * @param {object} call
+ * @param {number} call.id - Call id
+ * @param {string} call.peerJid - Who called
+ * @param {boolean} call.isVideo - if is video
+ * @param {boolean} call.isGroup - if is group
+ * @param {boolean} call.canHandleLocally - if we can handle in waweb
+ * @param {boolean} call.outgoing - if is outgoing
+ * @param {boolean} call.webClientShouldHandle - If Waweb should handle
+ * @param {object} call.participants - Participants
+ */
+ const cll = new Call(this, call);
+ this.emit(Events.INCOMING_CALL, cll);
+ });
+
+ await page.exposeFunction('onReaction', (reactions) => {
+ for (const reaction of reactions) {
+ /**
+ * Emitted when a reaction is sent, received, updated or removed
+ * @event Client#message_reaction
+ * @param {object} reaction
+ * @param {object} reaction.id - Reaction id
+ * @param {number} reaction.orphan - Orphan
+ * @param {?string} reaction.orphanReason - Orphan reason
+ * @param {number} reaction.timestamp - Timestamp
+ * @param {string} reaction.reaction - Reaction
+ * @param {boolean} reaction.read - Read
+ * @param {object} reaction.msgId - Parent message id
+ * @param {string} reaction.senderId - Sender id
+ * @param {?number} reaction.ack - Ack
+ */
+
+ this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
+ }
+ });
- this.authStrategy = new LegacySessionAuth({
- session: this.options.session,
- restartOnAuthFail: this.options.restartOnAuthFail,
+ await page.exposeFunction('onRemoveChatEvent', (chat) => {
+ /**
+ * Emitted when a chat is removed
+ * @event Client#chat_removed
+ * @param {Chat} chat
+ */
+ this.emit(Events.CHAT_REMOVED, new Chat(this, chat));
});
- } else {
- this.authStrategy = new NoAuth();
- }
- } else {
- this.authStrategy = this.options.authStrategy;
- }
-
- this.authStrategy.setup(this);
-
- this.pupBrowser = null;
- this.mPage = null;
-
- Util.setFfmpegPath(this.options.ffmpegPath);
- }
-
- /**
- * Sets up events and requirements, kicks off authentication request
- */
- async initialize() {
- let [browser, page] = [null, null];
-
- await this.authStrategy.beforeBrowserInitialized();
-
- const playwrightOpts = this.options.playwright;
- if (playwrightOpts && playwrightOpts.browserWSEndpoint) {
- browser = await playwright.chromium.connect(playwrightOpts.wsEndpoint, {
- timeout: 0,
- ...playwrightOpts,
- });
- page = await browser.newPage();
- } else {
- const browserArgs = [...(playwrightOpts.args || [])];
- if (!browserArgs.find((arg) => arg.includes("--user-agent"))) {
- browserArgs.push(`--user-agent=${this.options.userAgent}`);
- }
-
- browser = await playwright.chromium.launchPersistentContext(
- playwrightOpts.userDataDir,
- {
- ...playwrightOpts,
- args: browserArgs,
- timeout: 0,
+
+ await page.exposeFunction('onArchiveChatEvent', (chat, currState, prevState) => {
+ /**
+ * Emitted when a chat is archived/unarchived
+ * @event Client#chat_archived
+ * @param {Chat} chat
+ * @param {boolean} currState
+ * @param {boolean} prevState
+ */
+ this.emit(Events.CHAT_ARCHIVED, new Chat(this, chat), currState, prevState);
+ });
+
+ await page.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => {
+
+ if (msg.type === 'revoked') {
+ return;
+ }
+ /**
+ * Emitted when messages are edited
+ * @event Client#message_edit
+ * @param {Message} message
+ * @param {string} newBody
+ * @param {string} prevBody
+ */
+ this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
+ });
+
+ await page.evaluate(() => {
+ window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
+ window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
+ window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
+ window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); });
+ window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); });
+ window.Store.Msg.on('change:body', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); });
+ window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
+ window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
+ window.Store.Call.on('add', (call) => { window.onIncomingCall(call); });
+ window.Store.Chat.on('remove', async (chat) => { window.onRemoveChatEvent(await window.WWebJS.getChatModel(chat)); });
+ window.Store.Chat.on('change:archive', async (chat, currState, prevState) => { window.onArchiveChatEvent(await window.WWebJS.getChatModel(chat), currState, prevState); });
+ window.Store.Msg.on('add', (msg) => {
+ if (msg.isNewMsg) {
+ if (msg.type === 'ciphertext') {
+ // defer message event until ciphertext is resolved (type changed)
+ msg.once('change:type', (_msg) => window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg)));
+ } else {
+ window.onAddMessageEvent(window.WWebJS.getMessageModel(msg));
+ }
+ }
+ });
+ window.Store.Chat.on('change:unreadCount', (chat) => { window.onChatUnreadCountEvent(chat); });
+
+ {
+ const module = window.Store.createOrUpdateReactionsModule;
+ const ogMethod = module.createOrUpdateReactions;
+ module.createOrUpdateReactions = ((...args) => {
+ window.onReaction(args[0].map(reaction => {
+ const msgKey = window.Store.MsgKey.fromString(reaction.msgKey);
+ const parentMsgKey = window.Store.MsgKey.fromString(reaction.parentMsgKey);
+ const timestamp = reaction.timestamp / 1000;
+
+ return { ...reaction, msgKey, parentMsgKey, timestamp };
+ }));
+
+ return ogMethod(...args);
+ }).bind(module);
+ }
+ });
+
+ /**
+ * Emitted when the client has initialized and is ready to receive messages.
+ * @event Client#ready
+ */
+ this.emit(Events.READY);
+ this.authStrategy.afterAuthReady();
+
+ // Disconnect when navigating away when in PAIRING state (detect logout)
+ this.mPage.on('framenavigated', async () => {
+ const appState = await this.getState();
+ if (!appState || appState === WAState.PAIRING) {
+ await this.authStrategy.disconnect();
+ this.emit(Events.DISCONNECTED, 'NAVIGATION');
+ await this.destroy();
+ }
+ });
+ }
+
+ /**
+ * Closes the client
+ */
+ async destroy() {
+ await this.pupBrowser.close();
+ await this.authStrategy.destroy();
+ }
+
+
+ /*
+ #####################################
+ # UPDATE FUNCTION #
+ #####################################
+ */
+
+ /**
+ * logout sessions
+ */
+ async logout() {
+ await this.mPage.evaluate(() => {
+ return window.Store.AppState.logout();
+ });
+
+ await this.authStrategy.logout();
+ }
+
+ /**
+ * get detail whatsapp web
+ * @returns
+ */
+ async getWWeb() {
+ return await this.mPage.evaluate(() => {
+ var res = {
+ version: window.Debug.VERSION,
+ desktop_beta: window.Debug.DESKTOP_BETA,
+ id: window.Debug.BUILD_ID,
+ };
+ return res;
+ });
+ }
+
+ /**
+ * change name bot
+ * @param {*} name
+ * @returns
+ */
+ async changeName(name) {
+ try {
+ await this.mPage.evaluate((name) => {
+ return window.WWebJS.profile.setMyProfileName(name);
+ }, name);
+ return `successfully changed the bot name`;
+ } catch {
+ return `Can't change name`;
+ }
+ }
+
+ /**
+ * Mark as seen for the Chat
+ *@param {string} chatId
+ *@returns {Promise} result
+ *
+ */
+ async sendSeen(chatId) {
+ const result = await this.mPage.evaluate(async (chatId) => {
+ return window.WWebJS.sendSeen(chatId);
+ }, chatId);
+ return result;
+ }
+
+ /**
+ * Message options.
+ * @typedef {Object} MessageSendOptions
+ * @property {boolean} [linkPreview=true] - Show links preview. Has no effect on multi-device accounts.
+ * @property {boolean} [sendAudioAsVoice=false] - Send audio as voice message
+ * @property {boolean} [sendVideoAsGif=false] - Send video as gif
+ * @property {boolean} [sendMediaAsSticker=false] - Send media as a sticker
+ * @property {boolean} [sendMediaAsDocument=false] - Send media as a document
+ * @property {boolean} [parseVCards=true] - Automatically parse vCards and send them as contacts
+ * @property {string} [caption] - Image or video caption
+ * @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to)
+ * @property {Contact[]} [mentions] - Contacts that are being mentioned in the message
+ * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message
+ * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true).
+ * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
+ * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
+ * @property {MessageMedia} [media] - Media to be sent
+ */
+
+ /**
+ * Send a message to a specific chatId
+ * @param {string} chatId
+ * @param {string|MessageMedia|Location|Contact|Array|Buttons|List} content
+ * @param {MessageSendOptions} [options] - Options used when sending the message
+ *
+ * @returns {Promise} Message that was just sent
+ */
+ async sendMessage(chatId, content, options = {}) {
+ let internalOptions = {
+ linkPreview: options.linkPreview,
+ sendAudioAsVoice: options.ptt,
+ sendVideoAsGif: options.gifPlayBack,
+ sendMediaAsSticker: options.asSticker,
+ sendMediaAsDocument: options.asDocument,
+ caption: options.caption,
+ quotedMessageId: options.quoted ?.id ?
+ options.quoted._serialized || options.quoted.id._serialized : options.quoted,
+ parseVCards: options.parseVCards === false ? false : true,
+ mentionedJidList: Array.isArray(options.mentions) ?
+ options.mentions.map((contact) =>
+ contact ?.id ? contact ?.id ?._serialized : contact
+) : [],
+ extraOptions: options.extra,
+ };
+
+ if (options.caption) internalOptions.caption = options.caption;
+ const sendSeen =
+ typeof options.sendSeen === "undefined" ? true : options.sendSeen;
+
+ if (
+ Buffer.isBuffer(content) ||
+ /^[a-zA-Z0-9+/]*={0,2}$/i.test(content) ||
+ /^data:.*?\/.*?;base64,/i.test(content) ||
+ /^https?:\/\//.test(content) ||
+ Fs.existsSync(content)
+ ) {
+ let media = await Util.getFile(content);
+ let ex = typeof media === "undefined" ? ".bin" : media.ext;
+ if (!options.mimetype && ex === ".bin") {
+ content = content;
+ } else {
+ internalOptions.attachment = {
+ mimetype: options.mimetype ? options.mimetype : media.mime,
+ data: media ?.data ?.toString("base64") || Util.bufferToBase64(media.data),
+ filename: options.fileName ?
+ options.fileName : Util.getRandom(media.ext),
+ filesize: options.fileSize ? options.fileSize : media.size,
+ };
+ content = "";
+ }
+ } else if (content instanceof MessageMedia) {
+ internalOptions.attachment = content;
+ content = "";
+ } else if (options.media instanceof MessageMedia) {
+ internalOptions.attachment = options.media;
+ internalOptions.caption = content;
+ content = "";
+ } else if (content instanceof Location) {
+ internalOptions.location = content;
+ content = "";
+ } else if (content instanceof Contact) {
+ internalOptions.contactCard = content.id ?
+ content.id._serialized :
+ content;
+ content = "";
+ } else if (
+ Array.isArray(content) &&
+ content.length > 0 &&
+ content[0] instanceof Contact
+ ) {
+ internalOptions.contactCardList = content.map((contact) =>
+ contact.id ? contact.id._serialized : contact
+ );
+ content = "";
+ } else if (content instanceof Buttons) {
+ if (content.type !== "chat") {
+ internalOptions.attachment = content.body;
+ }
+ internalOptions.buttons = content;
+ content = "";
+ } else if (content instanceof List) {
+ internalOptions.list = content;
+ content = "";
+ }
+
+ if (internalOptions.sendMediaAsSticker && internalOptions.attachment) {
+ internalOptions.attachment = await Util.formatToWebpSticker(
+ internalOptions.attachment, {
+ packId: options ?.packId ? options.packId : global ?.Exif ?.packId,
+ packName: options ?.packName ?
+ options.packName : global ?.Exif ?.packName,
+ packPublish: options ?.packPublish ?
+ options.packPublish : global ?.Exif ?.packPublish,
+ packEmail: options ?.packEmail ?
+ options.packEmail : global ?.Exif ?.packEmail,
+ packWebsite: options ?.packWebsite ?
+ options.packWebsite : global ?.Exif ?.packWebsite,
+ androidApp: options ?.androidApp ?
+ options.androidApp : global ?.Exif ?.androidApp,
+ iOSApp: options ?.iOSApp ? options.iOSApp : global ?.Exif ?.iOSApp,
+ categories: options ?.categories ?
+ options.categories : global ?.Exif ?.categories,
+ isAvatar: options ?.isAvatar ?
+ options.isAvatar : global ?.Exif ?.isAvatar,
+ },
+ this.mPage
+ );
+ }
+
+ if (internalOptions.attachment ?.filesize > 42428800) {
+ let startDivision = 2;
+ let middle = internalOptions.attachment.data.length / startDivision;
+ let currentIndex = 0;
+
+
+ while (middle > (1024 * 1024 * 50)) {
+ startDivision += 1;
+ middle = Math.floor(internalOptions.attachment.data.length / startDivision);
+ }
+
+ const randomId = Util.generateHash(32);
+
+ while (currentIndex < internalOptions.attachment.data.length) {
+ let chunkPiece = middle;
+ if (currentIndex + middle > internalOptions.attachment.data.length) {
+ chunkPiece = internalOptions.attachment.data.length - currentIndex;
+ }
+ await this.mPage.evaluate(async ({
+ chatId,
+ chunk,
+ randomId
+ }) => {
+ if (chunk && window[`mediaChunk_${randomId}`]) {
+ window[`mediaChunk_${randomId}`] += chunk;
+ } else {
+ window[`mediaChunk_${randomId}`] = chunk;
+ }
+ }, {
+ chatId,
+ chunk: internalOptions.attachment.data.substring(currentIndex, currentIndex + chunkPiece),
+ randomId
+ });
+ currentIndex += chunkPiece;
+
+ }
+
+ internalOptions.attachment = new MessageMedia(internalOptions.attachment.mimetype, `mediaChunk_${randomId}`, internalOptions.attachment.filename, internalOptions.attachment.filesize);
+ }
+
+ const newMessage = await this.mPage.evaluate(
+ async ({
+ chatId,
+ message,
+ options,
+ sendSeen
+ }) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ const chat = await window.Store.Chat.find(chatWid);
+
+ if (sendSeen) {
+ window.WWebJS.sendSeen(chatId);
+ }
+
+ if (options ?.attachment ?.data ?.startsWith('mediaChunk')) {
+ options.attachment.data = window[options.attachment.data];
+ delete window[options.attachment.data];
+ }
+
+
+ const msg = await window.WWebJS.sendMessage(
+ chat,
+ message,
+ options,
+ sendSeen
+ );
+ return msg.serialize();
+ }, {
+ chatId,
+ message: content,
+ options: internalOptions,
+ sendSeen,
+ }
+ );
+
+ if (newMessage) return new Message(this, newMessage);
+ }
+
+ /**
+ * download media message
+ * @param {*} msg
+ * @returns
+ */
+ async downloadMediaMessage(msg) {
+ if (!Boolean(msg.mediaKey && msg.directPath))
+ throw new Error("Not Media Message");
+
+ const result = await this.mPage.evaluate(
+ async ({
+ directPath,
+ encFilehash,
+ filehash,
+ mediaKey,
+ type,
+ mediaKeyTimestamp,
+ mimetype,
+ filename,
+ size,
+ _serialized,
+ }) => {
+ try {
+ const decryptedMedia = await (
+ window.Store.DownloadManager ?.downloadAndMaybeDecrypt ||
+ window.Store.DownloadManager ?.downloadAndDecrypt
+)({
+ directPath,
+ encFilehash,
+ filehash,
+ mediaKey,
+ mediaKeyTimestamp,
+ type: type === "chat" ? mimetype.split("/")[0] || type : type,
+ signal: new AbortController().signal,
+ });
+
+ const data = await window.WWebJS.arrayBufferToBase64(decryptedMedia);
+
+ return {
+ data,
+ mimetype: mimetype,
+ filename: filename,
+ filesize: size,
+ };
+ } catch (e) {
+ const blob = await window.WWebJS.chat.downloadMedia(_serialized);
+ return {
+ data: await window.WWebJS.util.blobToBase64(blob),
+ mimetype: mimetype,
+ filename: filename,
+ filesize: size,
+ };
+ }
+ }, {
+ directPath: msg.directPath,
+ encFilehash: msg.encFilehash,
+ filehash: msg.filehash,
+ mediaKey: msg.mediaKey,
+ type: msg.type,
+ mediaKeyTimestamp: msg.mediaKeyTimestamp,
+ mimetype: msg.mime,
+ filename: msg.filename,
+ size: msg.fileSize,
+ _serialized: msg.id._serialized,
+ }
+ );
+
+ if (!result) return undefined;
+ return Util.base64ToBuffer(result ?.data);
+ }
+
+ /**
+ * download media and save
+ * @param {*} message
+ * @param {*} filename
+ * @returns
+ */
+ async downloadAndSaveMedia(message, filename) {
+ if (!message.isMedia) return;
+ const buffer = await this.downloadMediaMessage(message);
+ const getF = await Util.getFile(buffer)
+ filename = filename ? filename : Util.getRandom(getF.ext)
+ const folderPath = path.join(__dirname, "..", "..", "temp")
+ if (!Fs.existsSync(folderPath)) {
+ Fs.mkdirSync(folderPath);
+ console.log('Folder "temp" created!!');
+ }
+ const filePath = path.join(__dirname, "..", "..", "temp", filename);
+ return new Promise((resolve, reject) => {
+ Fs.writeFile(filePath, buffer, (err) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(filePath);
+ }
+ });
+ });
+ }
+
+ /**
+ * search messages
+ * @param {*} query
+ * @param {*} options
+ * @returns
+ */
+ async searchMessages(query, options = {}) {
+ const messages = await this.mPage.evaluate(
+ async ({
+ query,
+ page,
+ count,
+ remote
+ }) => {
+ const {
+ messages
+ } = await window.Store.Msg.search(
+ query,
+ page,
+ count,
+ remote
+ );
+ return messages.map((msg) => window.WWebJS.getMessageModel(msg));
+ }, {
+ query,
+ page: options.page,
+ limit: options.limit,
+ remote: options.chatId,
+ }
+ );
+
+ return messages.map((msg) => new Message(this, msg));
+ }
+
+ /**
+ * get all chats
+ * @returns
+ */
+ async getChats() {
+ let chats = await this.mPage.evaluate(async () => {
+ return await window.WWebJS.getChats();
+ });
+
+ return chats.map((chat) => ChatFactory.create(this, chat));
+ }
+
+ /**
+ * get chat by id
+ * @param {*} chatId
+ * @returns
+ */
+ async getChatById(chatId) {
+ let chat = await this.mPage.evaluate(async (chatId) => {
+ return await window.WWebJS.getChat(chatId);
+ }, chatId);
+
+ return ChatFactory.create(this, chat);
+ }
+
+ /**
+ * group metadata
+ * @param {*} chatId
+ * @returns
+ */
+ async groupMetadata(chatId) {
+ let chat = await this.mPage.evaluate(async (chatId) => {
+ let chatWid = await window.Store.WidFactory.createWid(chatId);
+ let chat = await window.Store.GroupMetadata.find(chatWid);
+
+ return chat.serialize();
+ }, chatId);
+
+ if (!chat) return false;
+ return chat;
+ }
+
+ /**
+ * get all contacts
+ * @returns
+ */
+ async getContacts() {
+ let contacts = await this.mPage.evaluate(() => {
+ return window.WWebJS.getContacts();
+ });
+
+ return contacts.map((contact) => ContactFactory.create(this, contact));
+ }
+
+ /**
+ * save contact
+ * @param {*} number
+ * @returns
+ */
+ async isSaveContact(number) {
+ let contact = await this.mPage.evaluate((number) => {
+ return window.WWebJS.getContact(number);
+ }, number);
+
+ let res = ContactFactory.create(this, contact);
+ return res.isMyContact;
+ }
+
+ /**
+ * get contact by id
+ * @param {*} contactId
+ * @returns
+ */
+ async getContactById(contactId) {
+ let contact = await this.mPage.evaluate((contactId) => {
+ return window.WWebJS.getContact(contactId);
+ }, contactId);
+
+ return ContactFactory.create(this, contact);
+ }
+
+ /**
+ * get invite info
+ * @param {*} inviteCode
+ * @returns
+ */
+ async getInviteInfo(inviteCode) {
+ return await this.mPage.evaluate((inviteCode) => {
+ return window.Store.GroupInvite.queryGroupInvite(inviteCode);
+ }, inviteCode);
+ }
+
+ /**
+ * accept invite
+ * @param {*} inviteCode
+ */
+ async acceptInvite(inviteCode) {
+ try {
+ return await this.mPage.evaluate((inviteCode) => {
+ return WPP.group.join(inviteCode)
+ }, inviteCode)
+ } catch {
+ return 'The request could not be processed.'
+ }
+ }
+
+ /**
+ * accept invite v4 message
+ * @param {*} inviteInfo
+ * @returns
+ */
+ async acceptInviteV4(inviteInfo) {
+ if (!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object';
+ if (inviteInfo.inviteCodeExp == 0) throw 'Expired invite code';
+ return this.mPage.evaluate(async inviteInfo => {
+ let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo;
+ let userWid = window.Store.WidFactory.createWid(fromId);
+ return await window.Store.GroupInviteV4.joinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, userWid);
+ }, inviteInfo);
+ }
+
+ /**
+ * set status bio
+ * @param {*} status
+ */
+ async setStatus(status) {
+ await this.mPage.evaluate(async (status) => {
+ return await window.Store.StatusUtils.setMyStatus(status);
+ }, status);
+ }
+
+ /**
+ * get state
+ * @returns
+ */
+ async getState() {
+ return await this.mPage.evaluate(() => {
+ if (!window.Store) return null;
+ return window.Store.AppState.state;
+ });
+ }
+
+ /**
+ * Marks the client as online
+ */
+ async sendPresenceAvailable() {
+ return await this.mPage.evaluate(() => {
+ return window.Store.PresenceUtils.sendPresenceAvailable();
+ });
+ }
+
+ /**
+ * Marks the client as unavailable
+ */
+ async sendPresenceUnavailable() {
+ return await this.mPage.evaluate(() => {
+ return window.Store.PresenceUtils.sendPresenceUnavailable();
+ });
+ }
+
+ /**
+ * archive chat
+ * @param {*} chatId
+ * @returns
+ */
+ async archiveChat(chatId) {
+ return await this.mPage.evaluate(async (chatId) => {
+ let chat = await window.Store.Chat.get(chatId);
+ await window.Store.Cmd.archiveChat(chat, true);
+ return true;
+ }, chatId);
+ }
+
+ /**
+ * unarchive chat
+ * @param {*} chatId
+ * @returns
+ */
+ async unarchiveChat(chatId) {
+ return await this.mPage.evaluate(async (chatId) => {
+ let chat = await window.Store.Chat.get(chatId);
+ await window.Store.Cmd.archiveChat(chat, false);
+ return false;
+ }, chatId);
+ }
+
+ /**
+ * pin chat
+ * @param {*} chatId
+ * @returns
+ */
+ async pinChat(chatId) {
+ return this.mPage.evaluate(async (chatId) => {
+ let chat = window.Store.Chat.get(chatId);
+ if (chat.pin) {
+ return true;
+ }
+ const MAX_PIN_COUNT = 3;
+ const chatModels = window.Store.Chat.getModelsArray();
+ if (chatModels.length > MAX_PIN_COUNT) {
+ let maxPinned = chatModels[MAX_PIN_COUNT - 1].pin;
+ if (maxPinned) {
+ return false;
+ }
+ }
+ await window.Store.Cmd.pinChat(chat, true);
+ return true;
+ }, chatId);
+ }
+
+ /**
+ * unpinchat
+ * @param {*} chatId
+ * @returns
+ */
+ async unpinChat(chatId) {
+ return this.mPage.evaluate(async (chatId) => {
+ let chat = window.Store.Chat.get(chatId);
+ if (!chat.pin) {
+ return false;
+ }
+ await window.Store.Cmd.pinChat(chat, false);
+ return false;
+ }, chatId);
+ }
+
+ /**
+ * mute chat
+ * @param {*} chatId
+ * @param {*} unmuteDate
+ */
+ async muteChat(chatId, unmuteDate) {
+ unmuteDate = unmuteDate ? unmuteDate : -1;
+ await this.mPage.evaluate(
+ async ({ chatId, timestamp }) => {
+ let chat = await window.Store.Chat.get(chatId);
+
+ let canMute = chat.mute.canMute();
+ if (!canMute) {
+ throw `Can't mute this chat`;
+ }
+
+ await chat.mute.mute({
+ expiration: timestamp,
+ sendDevice: !0,
+ });
+ }, {
+ chatId,
+ timestamp: unmuteDate || -1
+ }
+ );
+ }
+
+ /**
+ * unmute chat
+ * @param {*} chatId
+ */
+ async unmuteChat(chatId) {
+ await this.mPage.evaluate(async (chatId) => {
+ let chat = await window.Store.Chat.get(chatId);
+ await window.Store.Cmd.muteChat(chat, false);
+ }, chatId);
+ }
+
+ /**
+ * mark chat unread
+ * @param {*} chatId
+ */
+ async markChatUnread(chatId) {
+ await this.mPage.evaluate(async (chatId) => {
+ let chat = await window.Store.Chat.get(chatId);
+ await window.Store.Cmd.markChatUnread(chat, true);
+ }, chatId);
+ }
+
+ /**
+ * get profile picture
+ * @param {*} contactId
+ * @returns
+ */
+ async getProfilePict(contactId) {
+ const profilePic = await this.mPage.evaluate(async (contactId) => {
+ try {
+ const chatWid = window.Store.WidFactory.createWid(contactId);
+ return await window.Store.ProfilePic.profilePicFind(chatWid);
+ } catch (err) {
+ if (err.name === "ServerStatusCodeError") return undefined;
+ throw err;
+ }
+ }, contactId);
+
+ return profilePic ? profilePic.eurl : undefined;
+ }
+
+ /**
+ * Gets the Contact's common groups with you. Returns empty array if you don't have any common group.
+ * @param {string} contactId the whatsapp user's ID (_serialized format)
+ * @returns {Promise}
+ */
+ async getCommonGroups(contactId) {
+ const commonGroups = await this.mPage.evaluate(async (contactId) => {
+ let contact = window.Store.Contact.get(contactId);
+ if (!contact) {
+ const wid = window.Store.WidFactory.createUserWid(contactId);
+ const chatConstructor = window.Store.Contact.getModelsArray().find(
+ (c) => !c.isGroup
+ ).constructor;
+ contact = new chatConstructor({
+ id: wid,
+ });
+ }
+
+ if (contact.commonGroups) {
+ return contact.commonGroups.serialize();
+ }
+ const status = await window.Store.findCommonGroups(contact);
+ if (status) {
+ return contact.commonGroups.serialize();
+ }
+ return [];
+ }, contactId);
+ const chats = [];
+ for (const group of commonGroups) {
+ chats.push(group.id);
+ }
+ return chats;
+ }
+
+ /**
+ * Force reset of connection state for the client
+ */
+ async resetState() {
+ await this.mPage.evaluate(() => {
+ window.Store.AppState.phoneWatchdog.shiftTimer.forceRunNow();
+ });
+ }
+
+ /**
+ * Check if a given ID is registered in whatsapp
+ * @param {string} id the whatsapp user's ID
+ * @returns {Promise}
+ */
+ async isRegisteredUser(id) {
+ return Boolean(await this.getNumberId(id));
+ }
+
+ /**
+ * Get the registered WhatsApp ID for a number.
+ * Will return null if the number is not registered on WhatsApp.
+ * @param {string} number Number or ID ("@c.us" will be automatically appended if not specified)
+ * @returns {Promise}
+ */
+ async getNumberId(number) {
+ if (!number.endsWith("@c.us")) {
+ number += "@c.us";
}
- );
- page = (await browser.pages())[0];
- }
- if (this.options.proxyAuthentication !== undefined) {
- await page.authenticate(this.options.proxyAuthentication);
+ return await this.mPage.evaluate(async (number) => {
+ const wid = window.Store.WidFactory.createWid(number);
+ const result = await window.Store.QueryExist(wid);
+ if (!result || result.wid === undefined) return null;
+ return result.wid;
+ }, number);
}
- if (this.options.userAgent) {
- await page.setExtraHTTPHeaders({
- "User-Agent": this.options.userAgent,
- });
+ /**
+ * Get the formatted number of a WhatsApp ID.
+ * @param {string} number Number or ID
+ * @returns {Promise}
+ */
+ async getFormattedNumber(number) {
+ if (!number.endsWith("@s.whatsapp.net"))
+ number = number.replace("c.us", "s.whatsapp.net");
+ if (!number.includes("@s.whatsapp.net"))
+ number = `${number}@s.whatsapp.net`;
+
+ return await this.mPage.evaluate(async (numberId) => {
+ return window.Store.NumberInfo.formattedPhoneNumber(numberId);
+ }, number);
}
- if (this.options.bypassCSP) await page.setBypassCSP(true);
+ /**
+ * Get the country code of a WhatsApp ID.
+ * @param {string} number Number or ID
+ * @returns {Promise}
+ */
+ async getCountryCode(number) {
+ number = number.replace(" ", "").replace("+", "").replace("@c.us", "");
- this.pupBrowser = browser;
- this.mPage = page;
+ return await this.mPage.evaluate(async (numberId) => {
+ return window.Store.NumberInfo.findCC(numberId);
+ }, number);
+ }
- await this.authStrategy.afterBrowserInitialized();
+ /**
+ * Create a new group
+ * @param {string} name group title
+ * @param {Array} participants an array of Contacts or contact IDs to add to the group
+ * @returns {Object} createRes
+ * @returns {string} createRes.gid - ID for the group that was just created
+ * @returns {Object.} createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user's privacy settings don't allow you to add them to groups.
+ */
+ async createGroup(title, participants = [], options = {}) {
+ !Array.isArray(participants) && (participants = [participants]);
+ participants.map(p => (p instanceof Contact) ? p.id._serialized : p);
+
+ return await this.mPage.evaluate(async ({ title, participants, options }) => {
+ const { messageTimer = 0, parentGroupId, autoSendInviteV4 = true, comment = '' } = options;
+ const participantData = {}, participantWids = [], failedParticipants = [];
+ let createGroupResult, parentGroupWid;
+
+ const addParticipantResultCodes = {
+ default: 'An unknown error occupied while adding a participant',
+ 200: 'The participant was added successfully',
+ 403: 'The participant can be added by sending private invitation only',
+ 404: 'The phone number is not registered on WhatsApp'
+ };
- await page.goto(WhatsWebURL, {
- waitUntil: "load",
- timeout: 0,
- referer: "https://whatsapp.com/",
- });
+ for (const participant of participants) {
+ const pWid = window.Store.WidFactory.createWid(participant);
+ if ((await window.Store.QueryExist(pWid)) ?.wid) participantWids.push(pWid);
+ else failedParticipants.push(participant);
+ }
- await page.addScriptTag({
- path: require.resolve("@wppconnect/wa-js"),
- });
+ parentGroupId && (parentGroupWid = window.Store.WidFactory.createWid(parentGroupId));
+
+ try {
+ createGroupResult = await window.Store.GroupUtils.createGroup(
+ title,
+ participantWids,
+ messageTimer,
+ parentGroupWid
+ );
+ } catch (err) {
+ return 'CreateGroupError: An unknown error occupied while creating a group';
+ }
- // Wait WA-JS load
- await page.waitForFunction(() => window.WPP?.isReady);
+ for (const participant of createGroupResult.participants) {
+ let isInviteV4Sent = false;
+ const participantId = participant.wid._serialized;
+ const statusCode = participant.error ?? 200;
+
+ if (autoSendInviteV4 && statusCode === 403) {
+ window.Store.ContactCollection.gadd(participant.wid, { silent: true });
+ const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage(
+ await window.Store.Chat.find(participant.wid),
+ createGroupResult.wid._serialized,
+ createGroupResult.subject,
+ participant.invite_code,
+ participant.invite_code_exp,
+ comment,
+ await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid)
+ );
+ isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6')
+ ? addParticipantResult === 'OK'
+ : addParticipantResult.messageSendResult === 'OK';
+ }
- await page
- .evaluate(
- ({ markOnlineAvailable, isBeta }) => {
- WPP.chat.defaultSendMessageOptions.createChat = true;
- if (markOnlineAvailable) WPP.conn.setKeepAlive(markOnlineAvailable);
- if (isBeta) WPP.conn.joinWebBeta(true);
- },
- {
- markOnlineAvailable: this.options.markOnlineAvailable,
- isBeta: this.options.isBeta,
- }
- )
- .catch(() => false);
-
- await page.evaluate(() => {
- WPP.conn.setLimit("maxMediaSize", 16777216);
- WPP.conn.setLimit("maxFileSize", 104857600);
- WPP.conn.setLimit("maxShare", 100);
- WPP.conn.setLimit("statusVideoMaxDuration", 120);
- WPP.conn.setLimit("unlimitedPin", true);
- });
-
- await page.evaluate(`function getElementByXpath(path) {
-return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-}`);
-
- let lastPercent = null,
- lastPercentMessage = null;
-
- await page.exposeFunction("loadingScreen", async (percent, message) => {
- if (lastPercent !== percent || lastPercentMessage !== message) {
- this.emit(Events.LOADING_SCREEN, percent, message);
- lastPercent = percent;
- lastPercentMessage = message;
- }
- });
-
- await page.evaluate(
- async function (selectors) {
- var observer = new MutationObserver(function () {
- let progressBar = window.getElementByXpath(selectors.PROGRESS);
- let progressMessage = window.getElementByXpath(
- selectors.PROGRESS_MESSAGE
- );
-
- if (progressBar) {
- window.loadingScreen(progressBar.value, progressMessage.innerText);
- }
- });
+ participantData[participantId] = {
+ statusCode: statusCode,
+ message: addParticipantResultCodes[statusCode] || addParticipantResultCodes.default,
+ isGroupCreator: participant.type === 'superadmin',
+ isInviteV4Sent: isInviteV4Sent
+ };
+ }
- observer.observe(document, {
- attributes: true,
- childList: true,
- characterData: true,
- subtree: true,
- });
- },
- {
- PROGRESS: "//*[@id='app']/div/div/div[2]/progress",
- PROGRESS_MESSAGE: "//*[@id='app']/div/div/div[3]",
- }
- );
- console.log(
- `You Used Selector: ${
- this.options.selector ? this.options.selector : "default"
- }`
- );
- const INTRO_IMG_SELECTOR =
- this.options.selector == 1
- ? "div[role='textbox']"
- : this.options.selector == 2
- ? '[data-icon="chat"],[data-icon="intro-md-beta-logo-dark"],[data-icon="intro-md-beta-logo-light"]'
- : this.options.selector == 3
- ? "[data-icon='chat']"
- : this.options.selector == 4
- ? `['[data-icon*=community]', '[data-icon*=status]', '[data-icon*=community]', '[data-icon*=chat]', '[data-icon*=back]', '[data-icon*=search]', '[data-icon*=filter]', '[data-icon*=lock-small]', '[data-icon*=chat]']`
- : "[data-icon='search']";
-
- const INTRO_QRCODE_SELECTOR = "div[data-ref] canvas";
-
- // Checks which selector appears first
- const needAuthentication = await Promise.race([
- new Promise((resolve) => {
- page
- .waitForSelector(INTRO_IMG_SELECTOR, {
- timeout: this.options.authTimeoutMs,
- })
- .then(() => resolve(false))
- .catch((err) => resolve(err));
- }),
- new Promise((resolve) => {
- page
- .waitForSelector(INTRO_QRCODE_SELECTOR, {
- timeout: this.options.authTimeoutMs,
- })
- .then(() => resolve(true))
- .catch((err) => resolve(err));
- }),
- ]);
-
- // Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race;
- if (needAuthentication instanceof Error) throw needAuthentication;
-
- // Scan-qrcode selector was found. Needs authentication
- if (needAuthentication) {
- const { failed, failureEventPayload, restart } =
- await this.authStrategy.onAuthenticationNeeded();
- if (failed) {
- /**
- * Emitted when there has been an error while trying to restore an existing session
- * @event Client#auth_failure
- * @param {string} message
- */
- this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
- await this.destroy();
- if (restart) {
- // session restore failed so try again but without session to force new authentication
- return this.initialize();
- }
- return;
- }
-
- const loginQr = async () => {
- const QR_CONTAINER = "div[data-ref]";
- const QR_RETRY_BUTTON = "div[data-ref] > span > button";
- let qrRetries = 0;
- await page.exposeFunction("qrChanged", async (qr) => {
- /**
- * Emitted when a QR code is received
- * @event Client#qr
- * @param {string} qr QR Code
- */
- this.emit(Events.QR_RECEIVED, qr);
- if (this.options.qrMaxRetries > 0) {
- qrRetries++;
- if (qrRetries > this.options.qrMaxRetries) {
- this.emit(Events.DISCONNECTED, "Max qrcode retries reached");
- await this.destroy();
+ for (const f of failedParticipants) {
+ participantData[f] = {
+ statusCode: 404,
+ message: addParticipantResultCodes[404],
+ isGroupCreator: false,
+ isInviteV4Sent: false
+ };
}
- }
- });
- await page.evaluate(
- function (selectors) {
- const qr_container = document.querySelector(selectors.QR_CONTAINER);
- window.qrChanged(qr_container.dataset.ref);
+ return { title: title, gid: createGroupResult.wid, participants: participantData };
+ }, { title, participants, options });
+ }
+
- const obs = new MutationObserver((muts) => {
- muts.forEach((mut) => {
- // Listens to qr token change
- if (
- mut.type === "attributes" &&
- mut.attributeName === "data-ref"
- ) {
- window.qrChanged(mut.target.dataset.ref);
- }
- // Listens to retry button, when found, click it
- else if (mut.type === "childList") {
- const retry_button = document.querySelector(
- selectors.QR_RETRY_BUTTON
- );
- if (retry_button) retry_button.click();
- }
- });
- });
- obs.observe(qr_container.parentElement, {
- subtree: true,
- childList: true,
- attributes: true,
- attributeFilter: ["data-ref"],
- });
- },
- {
- QR_CONTAINER,
- QR_RETRY_BUTTON,
- }
- );
- };
-
- const loginPhone = async () => {
- const LINK_WITH_PHONE_BUTTON = "div._3rDmx div._2rQUO span._3iLTh";
- const PHONE_NUMBER_INPUT = "input.selectable-text";
- const NEXT_BUTTON = "div._1M6AF._3QJHf";
- const CODE_CONTAINER =
- '[aria-details="link-device-phone-number-code-screen-instructions"]';
- const GENERATE_NEW_CODE_BUTTON = '[data-testid="popup-controls-ok"]';
- const LINK_WITH_PHONE_VIEW = "div._1x9Rv._3qC8O";
-
- await page.exposeFunction("codeChanged", async (code) => {
- /**
- * Emitted when a QR code is received
- * @event Client#code
- * @param {string} code Code
- */
- this.emit(Events.CODE_RECEIVED, code);
+ /**
+ * Get all current Labels
+ * @returns {Promise>}
+ */
+ async getLabels() {
+ const labels = await this.mPage.evaluate(async () => {
+ return window.WWebJS.getLabels();
});
- const clickOnLinkWithPhoneButton = async () => {
- await page.waitForSelector(LINK_WITH_PHONE_BUTTON, {
- timeout: 0,
- });
- await page.click(LINK_WITH_PHONE_BUTTON);
- };
- const typePhoneNumber = async () => {
- await page.waitForSelector(PHONE_NUMBER_INPUT);
- const inputValue = await page.$eval(
- PHONE_NUMBER_INPUT,
- (el) => el.value
- );
- await page.click(PHONE_NUMBER_INPUT);
- for (let i = 0; i < inputValue.length; i++) {
- await page.keyboard.press("Backspace");
- }
- await page.type(
- PHONE_NUMBER_INPUT,
- this.options.linkingMethod.phone.number
- );
- };
+ return labels.map((data) => new Label(this, data));
+ }
+
+ /**
+ * Get Label instance by ID
+ * @param {string} labelId
+ * @returns {Promise}
+ */
+ async getLabelById(labelId) {
+ const label = await this.mPage.evaluate(async (labelId) => {
+ return window.WWebJS.getLabel(labelId);
+ }, labelId);
+
+ return new Label(this, label);
+ }
+
+ /**
+ * Get all Labels assigned to a chat
+ * @param {string} chatId
+ * @returns {Promise>}
+ */
+ async getChatLabels(chatId) {
+ const labels = await this.mPage.evaluate(async (chatId) => {
+ return window.WWebJS.getChatLabels(chatId);
+ }, chatId);
- await clickOnLinkWithPhoneButton();
- await typePhoneNumber();
- await page.click(NEXT_BUTTON);
+ return labels.map((data) => new Label(this, data));
+ }
- await page.evaluate(
- async function (selectors) {
- function waitForElementToExist(selector, timeout = 60000) {
- return new Promise((resolve, reject) => {
- if (document.querySelector(selector)) {
- return resolve(document.querySelector(selector));
+ /**
+ * Get all Chats for a specific Label
+ * @param {string} labelId
+ * @returns {Promise>}
+ */
+ async getChatsByLabelId(labelId) {
+ const chatIds = await this.mPage.evaluate(async (labelId) => {
+ const label = window.Store.Label.get(labelId);
+ const labelItems = label.labelItemCollection.getModelsArray();
+ return labelItems.reduce((result, item) => {
+ if (item.parentType === "Chat") {
+ result.push(item.parentId);
}
+ return result;
+ }, []);
+ }, labelId);
- const observer = new MutationObserver(() => {
- if (document.querySelector(selector)) {
- resolve(document.querySelector(selector));
- observer.disconnect();
- }
- });
+ return Promise.all(chatIds.map((id) => this.getChatById(id)));
+ }
- observer.observe(document.body, {
- subtree: true,
- childList: true,
- });
+ /**
+ * Gets all blocked contacts by host account
+ * @returns {Promise>}
+ */
+ async getBlockedContacts() {
+ const blockedContacts = await this.mPage.evaluate(() => {
+ let chatIds = window.Store.Blocklist.getModelsArray().map(
+ (a) => a.id._serialized
+ );
+ return Promise.all(chatIds.map((id) => window.WWebJS.getContact(id)));
+ });
- if (timeout > 0) {
- setTimeout(() => {
- reject(
- new Error(
- `waitForElementToExist: ${selector} not found in time`
- )
- );
- }, timeout);
+ return blockedContacts.map((contact) =>
+ ContactFactory.create(this.client, contact)
+ );
+ }
+
+ /**
+ * Sets the current user's profile picture.
+ * @param {MessageMedia} media
+ * @returns {Promise} Returns true if the picture was properly updated.
+ */
+ async setProfilePict(chatId, content, type = 'normal') {
+ let data
+ if ((Buffer.isBuffer(content) || /^data:.*?\/.*?;base64,/i.test(content) || /^https?:\/\//.test(content) || fs.existsSync(content))) {
+ let media = await Util.getFile(content)
+ if (type === 'long') {
+ data = {
+ img: await (await Util.resizeImage(media ?.data, 720)).toString('base64'),
+ preview: await (await Util.resizeImage(media ?.data, 120)).toString('base64')
+ }
+ } else if (type === 'normal') {
+ data = {
+ img: await (await Util.resizeImage(media ?.data, 640)).toString('base64'),
+ preview: await (await Util.resizeImage(media ?.data, 96)).toString('base64')
}
- });
}
+ }
- await waitForElementToExist(selectors.CODE_CONTAINER);
+ return this.mPage.evaluate(async ({ chatId, preview, image, type }) => {
+ let chatWid = await window.Store.WidFactory.createWid(chatId)
- const getCode = () => {
- const codeContainer = document.querySelector(
- selectors.CODE_CONTAINER
- );
- const code = Array.from(codeContainer.children)[0];
+ if (type === 'delete') return window.Store.GroupUtils.requestDeletePicture(chatWid)
- const cells = Array.from(code.children);
- return cells.map((cell) => cell.textContent).join("");
- };
- let code = getCode();
- window.codeChanged(code);
-
- const entirePageObserver = new MutationObserver(() => {
- const generateNewCodeButton = document.querySelector(
- selectors.GENERATE_NEW_CODE_BUTTON
- );
- if (generateNewCodeButton) {
- generateNewCodeButton.click();
- return;
- }
- });
- entirePageObserver.observe(document, {
- subtree: true,
- childList: true,
- });
+ return window.Store.GroupUtils.sendSetPicture(chatWid, image, preview)
+ }, { chatId, preview: data.img, image: data.preview, type })
+ }
- const linkWithPhoneView = document.querySelector(
- selectors.LINK_WITH_PHONE_VIEW
- );
- const linkWithPhoneViewObserver = new MutationObserver(() => {
- const newCode = getCode();
- if (newCode !== code) {
- window.codeChanged(newCode);
- code = newCode;
- }
- });
- linkWithPhoneViewObserver.observe(linkWithPhoneView, {
- subtree: true,
- childList: true,
- });
- },
- {
- CODE_CONTAINER,
- GENERATE_NEW_CODE_BUTTON,
- LINK_WITH_PHONE_VIEW,
- }
- );
- };
+ /**
+ * Deletes the current user's profile picture.
+ * @returns {Promise} Returns true if the picture was properly deleted.
+ */
+ async deleteMyPict() {
+ const success = await this.mPage.evaluate((chatid) => {
+ return window.WWebJS.deletePicture(chatid);
+ }, this.info.wid._serialized);
- const { linkingMethod } = this.options;
+ return success;
+ }
- if (linkingMethod.isQR()) {
- await loginQr();
- } else {
- await loginPhone();
- }
+ /*
+ #####################################
+ # NEW FUNCTION#
+ #####################################
+ */
- // Wait for code scan
- try {
- await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 });
- } catch (error) {
- if (
- error.name === "ProtocolError" &&
- error.message &&
- error.message.match(/Target closed/)
- ) {
- // something has called .destroy() while waiting
- return;
+ /**
+ * get detail host
+ * @returns
+ */
+ getHost() {
+ return this.mPage.evaluate(() => {
+ return WPP.whatsapp.Conn.attributes;
+ });
+ }
+
+ /**
+ * setting theme dark or light
+ * @param {*} type
+ * @returns
+ */
+ async setTheme(type = "dark") {
+ if (type !== "dark" && type !== "light") {
+ return {
+ status: false,
+ message: 'Invalid option. Only "dark" or "light" are allowed',
+ };
}
- throw error;
- }
- }
-
- await page.evaluate(() => {
- /**
- * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version.
- * @param {string} lOperand The left operand for the WWeb version string to compare with
- * @param {string} operator The comparison operator
- * @param {string} rOperand The right operand for the WWeb version string to compare with
- * @returns {boolean} Boolean value that indicates the result of the comparison
- */
- window.compareWwebVersions = (lOperand, operator, rOperand) => {
- if (![">", ">=", "<", "<=", "="].includes(operator)) {
- throw class _ extends Error {
- constructor(m) {
- super(m);
- this.name = "CompareWwebVersionsError";
- }
- }("Invalid comparison operator is provided");
+ try {
+ await this.mPage.evaluate(async (type) => {
+ await window.extra.theme[0].setTheme(type);
+ }, type);
+
+ return {
+ status: 200,
+ message: `Successfully changed to ${type} mode`,
+ };
+ } catch (error) {
+ return {
+ status: false,
+ message: "Can't change theme",
+ };
}
- if (typeof lOperand !== "string" || typeof rOperand !== "string") {
- throw class _ extends Error {
- constructor(m) {
- super(m);
- this.name = "CompareWwebVersionsError";
+ }
+
+ /**
+ * get theme waweb
+ * @returns
+ */
+ async getTheme() {
+ const theme = await this.mPage.evaluate(async () => {
+ if (window.localStorage) {
+ return await JSON.parse(JSON.stringify(window.localStorage)) ?.theme;
+ } else {
+ return await window.Store.Theme.getTheme();
}
- }("A non-string WWeb version type is provided");
- }
+ });
- lOperand = lOperand.replace(/-beta$/, "");
- rOperand = rOperand.replace(/-beta$/, "");
+ if (!theme) return false;
+ return theme;
+ }
- while (lOperand.length !== rOperand.length) {
- lOperand.length > rOperand.length
- ? (rOperand = rOperand.concat("0"))
- : (lOperand = lOperand.concat("0"));
+ /**
+ * join wa beta
+ * @param {*} act
+ * @returns
+ */
+ async joinBeta(act) {
+ const res = await this.mPage.evaluate((act) => {
+ return window.extra.joinBeta(act);
+ }, act);
+ if (act == true) {
+ return `successfully entered beta mode`;
+ } else if (act == false) {
+ return `managed to get out of beta mode`;
}
+ }
- lOperand = Number(lOperand.replace(/\./g, ""));
- rOperand = Number(rOperand.replace(/\./g, ""));
-
- return operator === ">"
- ? lOperand > rOperand
- : operator === ">="
- ? lOperand >= rOperand
- : operator === "<"
- ? lOperand < rOperand
- : operator === "<="
- ? lOperand <= rOperand
- : operator === "="
- ? lOperand === rOperand
- : false;
- };
- });
- await page.evaluate(ExposeStore, moduleRaid.toString());
- const authEventPayload = await this.authStrategy.getAuthEventPayload();
-
- /**
- * Emitted when authentication is successful
- * @event Client#authenticated
- */
- this.emit(Events.AUTHENTICATED, authEventPayload);
-
- // Check window.Store Injection
- await page
- .waitForFunction(() => {
- return (
- typeof window.WWebJS !== "undefined" &&
- typeof window.Store !== "undefined"
+ /**
+ * get member request
+ * @param {*} jid
+ * @returns
+ */
+ async getMemberRequest(jid) {
+ const res = await this.mPage.evaluate(async (jid) => {
+ return window.extra.group.memberRequest(jid);
+ }, jid);
+ return res;
+ }
+
+ /**
+ * approve member
+ * @param {*} jid
+ * @param {*} to
+ * @returns
+ */
+ async approveRequest(jid, to) {
+ const res = await this.mPage.evaluate(
+ ({
+ jid,
+ to
+ }) => {
+ return window.extra.group.approve(jid, to);
+ }, {
+ jid,
+ to,
+ }
);
- })
- .catch(() => false);
-
- await page.evaluate(async () => {
- // safely unregister service workers
- const registrations = await navigator.serviceWorker.getRegistrations();
- for (let registration of registrations) {
- registration.unregister();
- }
- });
-
- //Load util functions (serializers, helper functions)
- await page.evaluate(LoadUtils);
-
- // Expose client info
- /**
- * Current connection information
- * @type {ClientInfo}
- */
- this.info = new ClientInfo(
- this,
- await page.evaluate(() => {
- return {
- ...window.Store.Conn.serialize(),
- wid: window.Store.User.getMeUser(),
- };
- })
- );
-
- // Add InterfaceController
- this.interface = new InterfaceController(this);
-
- // Register events
- await page.exposeFunction("onAddMessageEvent", (msg) => {
- if (msg.type === "gp2") {
- const notification = new GroupNotification(this, msg);
- if (["add", "invite", "linked_group_join"].includes(msg.subtype)) {
- /**
- * Emitted when a user joins the chat via invite link or is added by an admin.
- * @event Client#group_join
- * @param {GroupNotification} notification GroupNotification with more information about the action
- */
- this.emit(Events.GROUP_JOIN, notification);
- } else if (msg.subtype === "remove" || msg.subtype === "leave") {
- /**
- * Emitted when a user leaves the chat or is removed by an admin.
- * @event Client#group_leave
- * @param {GroupNotification} notification GroupNotification with more information about the action
- */
- this.emit(Events.GROUP_LEAVE, notification);
- } else if (msg.subtype === "promote" || msg.subtype === "demote") {
- /**
- * Emitted when a current user is promoted to an admin or demoted to a regular user.
- * @event Client#group_admin_changed
- * @param {GroupNotification} notification GroupNotification with more information about the action
- */
- this.emit(Events.GROUP_ADMIN_CHANGED, notification);
+ return res;
+ }
+
+ /**
+ * reject member
+ * @param {*} jid
+ * @param {*} to
+ */
+ async rejectRequest(jid, to) {
+ const res = await this.mPage.evaluate(
+ ({
+ jid,
+ to
+ }) => {
+ return window.extra.group.reject(jid, to);
+ }, {
+ jid,
+ to,
+ }
+ );
+ }
+
+ /**
+ * send call
+ * @param {*} chatId
+ * @param {*} options
+ * @returns
+ */
+ async sendCall(chatId, options = {}) {
+ if (!Array.isArray(chatId)) {
+ chatId = [chatId];
} else {
- /**
- * Emitted when group settings are updated, such as subject, description or picture.
- * @event Client#group_update
- * @param {GroupNotification} notification GroupNotification with more information about the action
- */
- this.emit(Events.GROUP_UPDATE, notification);
- }
- return;
- }
-
- const message = new Message(this, msg);
-
- /**
- * Emitted when a new message is created, which may include the current user's own messages.
- * @event Client#message_create
- * @param {Message} message The message that was created
- */
- this.emit(Events.MESSAGE_CREATE, message);
-
- if (msg.id.fromMe) return;
-
- /**
- * Emitted when a new message is received.
- * @event Client#message
- * @param {Message} message The message that was received
- */
- this.emit(Events.MESSAGE_RECEIVED, message);
- });
-
- let last_message;
-
- await page.exposeFunction("onChangeMessageTypeEvent", (msg) => {
- if (msg.type === "revoked") {
- const message = new Message(this, msg);
- let revoked_msg;
- if (last_message && msg.id.id === last_message.id.id) {
- revoked_msg = new Message(this, last_message);
+ chatId = chatId;
}
- /**
- * Emitted when a message is deleted for everyone in the chat.
- * @event Client#message_revoke_everyone
- * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
- * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
- * Note that due to the way this data is captured, it may be possible that this param will be undefined.
- */
- this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
- }
- });
-
- await page.exposeFunction("onChangeMessageEvent", (msg) => {
- if (msg.type !== "revoked") {
- last_message = msg;
- }
-
- /**
- * The event notification that is received when one of
- * the group participants changes their phone number.
- */
- const isParticipant = msg.type === "gp2" && msg.subtype === "modify";
-
- /**
- * The event notification that is received when one of
- * the contacts changes their phone number.
- */
- const isContact =
- msg.type === "notification_template" && msg.subtype === "change_number";
-
- if (isParticipant || isContact) {
- /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
- const message = new Message(this, msg);
-
- const newId = isParticipant ? msg.recipients[0] : msg.to;
- const oldId = isParticipant
- ? msg.author
- : msg.templateParams.find((id) => id !== newId);
+ const call = await Promise.all(
+ chatId.map(async (id) => {
+ return await this.mPage.evaluate(
+ ({
+ id,
+ options
+ }) => {
+ return window.WWebJS.call.offer(id, options);
+ }, {
+ id,
+ options,
+ }
+ );
+ })
+ );
- /**
- * Emitted when a contact or a group participant changes their phone number.
- * @event Client#contact_changed
- * @param {Message} message Message with more information about the event.
- * @param {String} oldId The user's id (an old one) who changed their phone number
- * and who triggered the notification.
- * @param {String} newId The user's new id after the change.
- * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
- */
- this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
- }
- });
-
- await page.exposeFunction("onRemoveMessageEvent", (msg) => {
- if (!msg.isNewMsg) return;
-
- const message = new Message(this, msg);
-
- /**
- * Emitted when a message is deleted by the current user.
- * @event Client#message_revoke_me
- * @param {Message} message The message that was revoked
- */
- this.emit(Events.MESSAGE_REVOKED_ME, message);
- });
-
- await page.exposeFunction("onMessageAckEvent", (msg, ack) => {
- const message = new Message(this, msg);
-
- /**
- * Emitted when an ack event occurrs on message type.
- * @event Client#message_ack
- * @param {Message} message The message that was affected
- * @param {MessageAck} ack The new ACK value
- */
- this.emit(Events.MESSAGE_ACK, message, ack);
- });
-
- await page.exposeFunction("onChatUnreadCountEvent", async (data) => {
- const chat = await this.getChatById(data.id);
-
- /**
- * Emitted when the chat unread count changes
- */
- this.emit(Events.UNREAD_COUNT, chat);
- });
-
- await page.exposeFunction("onMessageMediaUploadedEvent", (msg) => {
- const message = new Message(this, msg);
-
- /**
- * Emitted when media has been uploaded for a message sent by the client.
- * @event Client#media_uploaded
- * @param {Message} message The message with media that was uploaded
- */
- this.emit(Events.MEDIA_UPLOADED, message);
- });
-
- await page.exposeFunction("onAppStateChangedEvent", async (state) => {
- /**
- * Emitted when the connection state changes
- * @event Client#change_state
- * @param {WAState} state the new connection state
- */
- this.emit(Events.STATE_CHANGED, state);
-
- const ACCEPTED_STATES = [
- WAState.CONNECTED,
- WAState.OPENING,
- WAState.PAIRING,
- WAState.TIMEOUT,
- ];
-
- if (this.options.takeoverOnConflict) {
- ACCEPTED_STATES.push(WAState.CONFLICT);
-
- if (state === WAState.CONFLICT) {
- setTimeout(() => {
- this.pupPage.evaluate(() => window.Store.AppState.takeover());
- }, this.options.takeoverTimeoutMs);
- }
- }
+ return chatId.length;
+ }
- if (!ACCEPTED_STATES.includes(state)) {
- /**
- * Emitted when the client has been disconnected
- * @event Client#disconnected
- * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect
- */
- await this.authStrategy.disconnect();
- this.emit(Events.DISCONNECTED, state);
- this.destroy();
- }
- });
-
- await page.exposeFunction("onBatteryStateChangedEvent", (state) => {
- const { battery, plugged } = state;
-
- if (battery === undefined) return;
-
- /**
- * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
- * @event Client#change_battery
- * @param {object} batteryInfo
- * @param {number} batteryInfo.battery - The current battery percentage
- * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
- * @deprecated
- */
- this.emit(Events.BATTERY_CHANGED, { battery, plugged });
- });
-
- await page.exposeFunction("onIncomingCall", (call) => {
- /**
- * Emitted when a call is received
- * @event Client#incoming_call
- * @param {object} call
- * @param {number} call.id - Call id
- * @param {string} call.peerJid - Who called
- * @param {boolean} call.isVideo - if is video
- * @param {boolean} call.isGroup - if is group
- * @param {boolean} call.canHandleLocally - if we can handle in waweb
- * @param {boolean} call.outgoing - if is outgoing
- * @param {boolean} call.webClientShouldHandle - If Waweb should handle
- * @param {object} call.participants - Participants
- */
- const cll = new Call(this, call);
- this.emit(Events.INCOMING_CALL, cll);
- });
-
- await page.exposeFunction("onPollVote", (vote) => {
- const vote_ = new PollVote(this, vote);
- /**
- * Emitted when a poll vote is received
- * @event Client#poll_vote
- * @param {object} vote
- * @param {string} vote.sender Sender of the vote
- * @param {number} vote.senderTimestampMs Timestamp the vote was sent
- * @param {Array} vote.selectedOptions Options selected
- */
- this.emit(Events.POLL_VOTE, vote_);
- });
-
- await page.exposeFunction("onReaction", (reactions) => {
- for (const reaction of reactions) {
- /**
- * Emitted when a reaction is sent, received, updated or removed
- * @event Client#message_reaction
- * @param {object} reaction
- * @param {object} reaction.id - Reaction id
- * @param {number} reaction.orphan - Orphan
- * @param {?string} reaction.orphanReason - Orphan reason
- * @param {number} reaction.timestamp - Timestamp
- * @param {string} reaction.reaction - Reaction
- * @param {boolean} reaction.read - Read
- * @param {object} reaction.msgId - Parent message id
- * @param {string} reaction.senderId - Sender id
- * @param {?number} reaction.ack - Ack
- */
+ /**
+ * end calling
+ * @param {*} chatId
+ * @returns
+ */
+ async endCall(chatId) {
+ const end = await this.mPage.evaluate((chatId) => {
+ return window.WWebJS.call.end(chatId);
+ }, chatId);
- this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
- }
- });
-
- await page.exposeFunction("onRemoveChatEvent", (chat) => {
- /**
- * Emitted when a chat is removed
- * @event Client#chat_removed
- * @param {Chat} chat
- */
- this.emit(Events.CHAT_REMOVED, new Chat(this, chat));
- });
-
- await page.exposeFunction(
- "onArchiveChatEvent",
- (chat, currState, prevState) => {
- /**
- * Emitted when a chat is archived/unarchived
- * @event Client#chat_archived
- * @param {Chat} chat
- * @param {boolean} currState
- * @param {boolean} prevState
- */
- this.emit(
- Events.CHAT_ARCHIVED,
- new Chat(this, chat),
- currState,
- prevState
- );
- }
- );
-
- await page.exposeFunction(
- "onEditMessageEvent",
- (msg, newBody, prevBody) => {
- if (msg.type === "revoked") {
- return;
+ if (!end) return false;
+ return true;
+ }
+
+ /**
+ * accept call
+ * @param {*} chatId
+ * @returns
+ */
+ async acceptCall(chatId) {
+ const end = await this.mPage.evaluate((chatId) => {
+ return window.WWebJS.call.accept(chatId);
+ }, chatId);
+
+ if (!end) return false;
+ return true;
+ }
+
+ /**
+ * send story text
+ * @param {*} text
+ * @param {*} bg
+ * @param {*} fonts
+ * @returns
+ */
+ async sendStoryText(text, bg, fonts) {
+ if (!text) return "Input story text";
+ if (!bg) return "Input background color (hex)";
+ if (!fonts) return "Input style font (number)";
+ try {
+ const res = await this.mPage.evaluate(
+ async ({
+ text,
+ bg,
+ fonts
+ }) => {
+ return window.extra.status.text(text, {
+ backgroundColor: bg,
+ font: fonts,
+ });
+ }, {
+ text,
+ bg,
+ fonts,
+ }
+ );
+ return "Successfully sent status text to WhatsApp";
+ } catch (error) {
+ return "Failed to send status text to WhatsApp";
}
- /**
- * Emitted when messages are edited
- * @event Client#message_edit
- * @param {Message} message
- * @param {string} newBody
- * @param {string} prevBody
- */
- this.emit(
- Events.MESSAGE_EDIT,
- new Message(this, msg),
- newBody,
- prevBody
- );
- }
- );
-
- await page.evaluate(() => {
- window.Store.Msg.on("change", (msg) => {
- window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg));
- });
- window.Store.Msg.on("change:type", (msg) => {
- window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg));
- });
- window.Store.Msg.on("change:ack", (msg, ack) => {
- window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack);
- });
- window.Store.Msg.on("change:isUnsentMedia", (msg, unsent) => {
- if (msg.id.fromMe && !unsent)
- window.onMessageMediaUploadedEvent(
- window.WWebJS.getMessageModel(msg)
- );
- });
- window.Store.Msg.on("remove", (msg) => {
- if (msg.isNewMsg)
- window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg));
- });
- window.Store.Msg.on("change:body", (msg, newBody, prevBody) => {
- window.onEditMessageEvent(
- window.WWebJS.getMessageModel(msg),
- newBody,
- prevBody
+ }
+
+ /**
+ * get name whatsapp
+ * @param {*} jid
+ * @returns
+ */
+ async getName(jid) {
+ const contact = await this.getContactById(jid);
+ return (
+ contact.name || contact.pushname || contact.shortName || contact.number
);
- });
- window.Store.AppState.on("change:state", (_AppState, state) => {
- window.onAppStateChangedEvent(state);
- });
- window.Store.Conn.on("change:battery", (state) => {
- window.onBatteryStateChangedEvent(state);
- });
- window.Store.Call.on("add", (call) => {
- window.onIncomingCall(call);
- });
- window.Store.Chat.on("remove", async (chat) => {
- window.onRemoveChatEvent(await window.WWebJS.getChatModel(chat));
- });
- window.Store.Chat.on(
- "change:archive",
- async (chat, currState, prevState) => {
- window.onArchiveChatEvent(
- await window.WWebJS.getChatModel(chat),
- currState,
- prevState
- );
+ }
+
+ /**
+ * send file
+ * @param {sen} chatId
+ * @param {*} pathOrBase64
+ * @param {*} nameOrOptions
+ */
+ async sendFile(chatId, pathOrBase64, nameOrOptions) {
+ if (typeof nameOrOptions === "string") {
+ options.filename = nameOrOptions;
+ nameOrOptions = {};
}
- );
- window.Store.Msg.on("add", (msg) => {
- if (msg.isNewMsg) {
- if (msg.type === "ciphertext") {
- // defer message event until ciphertext is resolved (type changed)
- msg.once("change:type", (_msg) =>
- window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg))
- );
- } else {
- window.onAddMessageEvent(window.WWebJS.getMessageModel(msg));
- }
+
+ const fileContent = Util.getFile(pathOrBase64);
+ var view = nameOrOptions.view ? true : false;
+ var ptt = nameOrOptions.ptt ? true : false;
+ let options = {
+ type: nameOrOptions ?.type ? nameOrOptions.type : "auto-detect",
+ filename: nameOrOptions ?.filename ? nameOrOptions.filename : "",
+ mimetype: fileContent.mime,
+ isViewOnce: view,
+ isPtt: ptt,
+ };
+
+ const base64 = `data:${(await fileContent).mime};base64,${(
+ await fileContent
+ ).data.toString("base64")}`;
+
+ if (!!nameOrOptions ?.quoted) {
+ options.quotedMsg =
+ typeof nameOrOptions.quoted === "object" ?
+ nameOrOptions.quoted.id._serialized :
+ nameOrOptions.quoted || nameOrOptions.quoted._serialized;
+
+ delete nameOrOptions.quoted;
}
- });
- window.Store.PollVote.on("add", (vote) => {
- if (vote.parentMsgKey)
- vote.pollCreationMessage = window.Store.Msg.get(
- vote.parentMsgKey
- ).serialize();
- window.onPollVote(vote);
- });
- window.Store.Chat.on("change:unreadCount", (chat) => {
- window.onChatUnreadCountEvent(chat);
- });
- {
- const module = window.Store.createOrUpdateReactionsModule;
- const ogMethod = module.createOrUpdateReactions;
- module.createOrUpdateReactions = ((...args) => {
- window.onReaction(
- args[0].map((reaction) => {
- const msgKey = window.Store.MsgKey.fromString(reaction.msgKey);
- const parentMsgKey = window.Store.MsgKey.fromString(
- reaction.parentMsgKey
- );
- const timestamp = reaction.timestamp / 1000;
-
- return { ...reaction, msgKey, parentMsgKey, timestamp };
- })
- );
-
- return ogMethod(...args);
- }).bind(module);
- }
- });
-
- /**
- * Emitted when the client has initialized and is ready to receive messages.
- * @event Client#ready
- */
- this.emit(Events.READY);
- this.authStrategy.afterAuthReady();
-
- // Disconnect when navigating away when in PAIRING state (detect logout)
- this.mPage.on("framenavigated", async () => {
- const appState = await this.getState();
- if (!appState || appState === WAState.PAIRING) {
- await this.authStrategy.disconnect();
- this.emit(Events.DISCONNECTED, "NAVIGATION");
- await this.destroy();
- }
- });
- }
-
- /**
- * Closes the client
- */
- async destroy() {
- await this.pupBrowser.close();
- await this.authStrategy.destroy();
- }
-
- /*
-#####################################
-# UPDATE FUNCTION #
-#####################################
-*/
-
- /**
- * logout sessions
- */
- async logout() {
- await this.mPage.evaluate(() => {
- return window.Store.AppState.logout();
- });
-
- await this.authStrategy.logout();
- }
-
- /**
- * get detail whatsapp web
- * @returns
- */
- async getWWeb() {
- return await this.mPage.evaluate(() => {
- var res = {
- version: window.Debug.VERSION,
- desktop_beta: window.Debug.DESKTOP_BETA,
- id: window.Debug.BUILD_ID,
- };
- return res;
- });
- }
-
- /**
- * change name bot
- * @param {*} name
- * @returns
- */
- async changeMyname(name) {
- try {
- await this.mPage.evaluate((name) => {
- return window.WWebJS.profile.setMyProfileName(name);
- }, name);
- return `successfully changed the bot name`;
- } catch {
- return `Can't change name`;
- }
- }
-
- /**
- * Mark as seen for the Chat
- *@param {string} chatId
- *@returns {Promise} result
- *
- */
- async sendSeen(chatId) {
- const result = await this.mPage.evaluate(async (chatId) => {
- return window.WWebJS.sendSeen(chatId);
- }, chatId);
- return result;
- }
-
- /**
- * Message options.
- * @typedef {Object} MessageSendOptions
- * @property {boolean} [linkPreview=true] - Show links preview. Has no effect on multi-device accounts.
- * @property {boolean} [sendAudioAsVoice=false] - Send audio as voice message
- * @property {boolean} [sendVideoAsGif=false] - Send video as gif
- * @property {boolean} [sendMediaAsSticker=false] - Send media as a sticker
- * @property {boolean} [sendMediaAsDocument=false] - Send media as a document
- * @property {boolean} [parseVCards=true] - Automatically parse vCards and send them as contacts
- * @property {string} [caption] - Image or video caption
- * @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to)
- * @property {Contact[]} [mentions] - Contacts that are being mentioned in the message
- * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message
- * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true).
- * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
- * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
- * @property {MessageMedia} [media] - Media to be sent
- */
-
- /**
- * Send a message to a specific chatId
- * @param {string} chatId
- * @param {string|MessageMedia|Location|Contact|Array|Buttons|List} content
- * @param {MessageSendOptions} [options] - Options used when sending the message
- *
- * @returns {Promise} Message that was just sent
- */
- async sendMessage(chatId, content, options = {}) {
- let internalOptions = {
- linkPreview: options.linkPreview,
- sendAudioAsVoice: options.ptt,
- sendVideoAsGif: options.gifPlayBack,
- sendMediaAsSticker: options.asSticker,
- sendMediaAsDocument: options.asDocument,
- caption: options.caption,
- quotedMessageId: options.quoted?.id
- ? options.quoted._serialized || options.quoted.id._serialized
- : options.quoted,
- parseVCards: options.parseVCards === false ? false : true,
- mentionedJidList: Array.isArray(options.mentions)
- ? options.mentions.map((contact) =>
- contact?.id ? contact?.id?._serialized : contact
- )
- : [],
- extraOptions: options.extra,
- };
-
- if (options.caption) internalOptions.caption = options.caption;
- const sendSeen =
- typeof options.sendSeen === "undefined" ? true : options.sendSeen;
-
- if (
- Buffer.isBuffer(content) ||
- /^[a-zA-Z0-9+/]*={0,2}$/i.test(content) ||
- /^data:.*?\/.*?;base64,/i.test(content) ||
- /^https?:\/\//.test(content) ||
- Fs.existsSync(content)
- ) {
- let media = await Util.getFile(content);
- let ex = typeof media === "undefined" ? ".bin" : media.ext;
- if (!options.mimetype && ex === ".bin") {
- content = content;
- } else {
- internalOptions.attachment = {
- mimetype: options.mimetype ? options.mimetype : media.mime,
- data:
- media?.data?.toString("base64") || Util.bufferToBase64(media.data),
- filename: options.fileName
- ? options.fileName
- : Util.getRandom(media.ext),
- filesize: options.fileSize ? options.fileSize : media.size,
+
+ if (nameOrOptions ?.mentions) {
+ options.mentionedJidList = Array.isArray(options.mentions) ?
+ options.mentions.map((contact) =>
+ contact ?.id ? contact ?.id ?._serialized : contact
+) : [];
+
+ delete nameOrOptions.mentions;
+ }
+
+ options = {
+ ...nameOrOptions,
+ ...options,
};
- content = "";
- }
- } else if (content instanceof MessageMedia) {
- internalOptions.attachment = content;
- content = "";
- } else if (options.media instanceof MessageMedia) {
- internalOptions.attachment = options.media;
- internalOptions.caption = content;
- content = "";
- } else if (content instanceof Location) {
- internalOptions.location = content;
- content = "";
- } else if (content instanceof Contact) {
- internalOptions.contactCard = content.id
- ? content.id._serialized
- : content;
- content = "";
- } else if (
- Array.isArray(content) &&
- content.length > 0 &&
- content[0] instanceof Contact
- ) {
- internalOptions.contactCardList = content.map((contact) =>
- contact.id ? contact.id._serialized : contact
- );
- content = "";
- } else if (content instanceof Buttons) {
- if (content.type !== "chat") {
- internalOptions.attachment = content.body;
- }
- internalOptions.buttons = content;
- content = "";
- } else if (content instanceof List) {
- internalOptions.list = content;
- content = "";
- }
-
- if (internalOptions.sendMediaAsSticker && internalOptions.attachment) {
- internalOptions.attachment = await Util.formatToWebpSticker(
- internalOptions.attachment,
- {
- packId: options?.packId ? options.packId : global?.Exif?.packId,
- packName: options?.packName
- ? options.packName
- : global?.Exif?.packName,
- packPublish: options?.packPublish
- ? options.packPublish
- : global?.Exif?.packPublish,
- packEmail: options?.packEmail
- ? options.packEmail
- : global?.Exif?.packEmail,
- packWebsite: options?.packWebsite
- ? options.packWebsite
- : global?.Exif?.packWebsite,
- androidApp: options?.androidApp
- ? options.androidApp
- : global?.Exif?.androidApp,
- iOSApp: options?.iOSApp ? options.iOSApp : global?.Exif?.iOSApp,
- categories: options?.categories
- ? options.categories
- : global?.Exif?.categories,
- isAvatar: options?.isAvatar
- ? options.isAvatar
- : global?.Exif?.isAvatar,
- },
- this.mPage
- );
- }
-
- if (internalOptions.attachment?.filesize > 42428800) {
- let startDivision = 2;
- let middle = internalOptions.attachment.data.length / startDivision;
- let currentIndex = 0;
-
- while (middle > 1024 * 1024 * 50) {
- startDivision += 1;
- middle = Math.floor(
- internalOptions.attachment.data.length / startDivision
+
+ const msg = await this.mPage.evaluate(
+ async ({
+ chatId,
+ base64,
+ options
+ }) => {
+ return WPP.chat.sendFileMessage(chatId, base64, options);
+ }, {
+ chatId,
+ base64,
+ options
+ }
);
- }
+ }
- const randomId = Util.generateHash(32);
+ /**
+ * clear message
+ * @param {*} chatId
+ * @returns
+ */
+ async clearMessage(chatId) {
+ return this.mPage.evaluate((chatId) => {
+ return window.WWebJS.sendClearChat(chatId);
+ }, chatId);
+ }
- while (currentIndex < internalOptions.attachment.data.length) {
- let chunkPiece = middle;
- if (currentIndex + middle > internalOptions.attachment.data.length) {
- chunkPiece = internalOptions.attachment.data.length - currentIndex;
- }
+ /**
+ * send read status
+ * @param {*} chatId
+ * @param {*} statusId
+ */
+ async sendReadStatus(chatId, statusId) {
await this.mPage.evaluate(
- async ({ chatId, chunk, randomId }) => {
- if (chunk && window[`mediaChunk_${randomId}`]) {
- window[`mediaChunk_${randomId}`] += chunk;
- } else {
- window[`mediaChunk_${randomId}`] = chunk;
+ async ({
+ chatId,
+ statusId
+ }) => {
+ const wid = window.Store.WidFactory.createWid(chatId);
+ const statusStore = window.Store.StatusV3.get(wid);
+
+ const status = statusStore ?.msgs.get(statusId);
+ await statusStore ?.sendReadStatus(
+ status,
+ status ?.mediaKeyTimestamp || status ?.t
+);
+ }, {
+ chatId,
+ statusId,
}
- },
- {
- chatId,
- chunk: internalOptions.attachment.data.substring(
- currentIndex,
- currentIndex + chunkPiece
- ),
- randomId,
- }
);
- currentIndex += chunkPiece;
- }
-
- internalOptions.attachment = new MessageMedia(
- internalOptions.attachment.mimetype,
- `mediaChunk_${randomId}`,
- internalOptions.attachment.filename,
- internalOptions.attachment.filesize
- );
}
- const newMessage = await this.mPage.evaluate(
- async ({ chatId, message, options, sendSeen }) => {
- const chatWid = window.Store.WidFactory.createWid(chatId);
- const chat = await window.Store.Chat.find(chatWid);
+ /**
+ * get story
+ * @param {*} chatId
+ * @returns
+ */
+ async getStories(chatId = this.info.wid._serialized) {
+ const message = await this.mPage.evaluate((chatId) => {
+ if (chatId === "all") {
+ const status = window.Store.StatusV3.getModelsArray();
- if (sendSeen) {
- window.WWebJS.sendSeen(chatId);
- }
+ if (!status) return undefined;
+ return status.map((a) => a.serialize());
+ } else {
+ const Wid = window.Store.WidFactory.createWid(chatId);
+ const status = window.Store.StatusV3.get(Wid);
- if (options?.attachment?.data?.startsWith("mediaChunk")) {
- options.attachment.data = window[options.attachment.data];
- delete window[options.attachment.data];
- }
+ if (!status) return new Error("No Status Found!");
+ const msg = status.serialize();
+ return [msg];
+ }
+ }, chatId);
- const msg = await window.WWebJS.sendMessage(
- chat,
- message,
- options,
- sendSeen
- );
- return msg.serialize();
- },
- {
- chatId,
- message: content,
- options: internalOptions,
- sendSeen,
- }
- );
-
- if (newMessage) return new Message(this, newMessage);
- }
-
- /**
- * download media message
- * @param {*} msg
- * @returns
- */
- async downloadMediaMessage(msg) {
- if (!Boolean(msg.mediaKey && msg.directPath))
- throw new Error("Not Media Message");
-
- const result = await this.mPage.evaluate(
- async ({
- directPath,
- encFilehash,
- filehash,
- mediaKey,
- type,
- mediaKeyTimestamp,
- mimetype,
- filename,
- size,
- _serialized,
- }) => {
- try {
- const decryptedMedia = await (
- window.Store.DownloadManager?.downloadAndMaybeDecrypt ||
- window.Store.DownloadManager?.downloadAndDecrypt
- )({
- directPath,
- encFilehash,
- filehash,
- mediaKey,
- mediaKeyTimestamp,
- type: type === "chat" ? mimetype.split("/")[0] || type : type,
- signal: new AbortController().signal,
- });
-
- const data = await window.WWebJS.arrayBufferToBase64(decryptedMedia);
-
- return {
- data,
- mimetype: mimetype,
- filename: filename,
- filesize: size,
- };
- } catch (e) {
- const blob = await window.WWebJS.chat.downloadMedia(_serialized);
- return {
- data: await window.WWebJS.util.blobToBase64(blob),
- mimetype: mimetype,
- filename: filename,
- filesize: size,
- };
- }
- },
- {
- directPath: msg.directPath,
- encFilehash: msg.encFilehash,
- filehash: msg.filehash,
- mediaKey: msg.mediaKey,
- type: msg.type,
- mediaKeyTimestamp: msg.mediaKeyTimestamp,
- mimetype: msg.mime,
- filename: msg.filename,
- size: msg.fileSize,
- _serialized: msg.id._serialized,
- }
- );
-
- if (!result) return undefined;
- return Util.base64ToBuffer(result?.data);
- }
-
- /**
- * download media and save
- * @param {*} message
- * @param {*} filename
- * @returns
- */
- async downloadAndSaveMediaMessage(message, filename) {
- if (!message.isMedia) return;
-
- filename = filename
- ? filename
- : Util.getRandom(
- extension(message?.mime || message._data.mimetype || message.mimetype)
- );
- const buffer = await this.downloadMediaMessage(message);
- const filePath = join(__dirname, "..", "..", "temp", filename);
- await fs.writeFile(filePath, buffer);
-
- return filePath;
- }
-
- /**
- * search messages
- * @param {*} query
- * @param {*} options
- * @returns
- */
- async searchMessages(query, options = {}) {
- const messages = await this.mPage.evaluate(
- async ({ query, page, count, remote }) => {
- const { messages } = await window.Store.Msg.search(
- query,
- page,
- count,
- remote
+ if (!message === undefined) return undefined;
+ return message;
+ }
+
+ /**
+ * get contact by name
+ * @param {*} name
+ * @returns
+ */
+ async getContactByName(name) {
+ let contact = (await this.getContacts()).filter(
+ (a) =>
+ a.name && (a.name.toLowerCase().includes(name) || a.name.includes(name))
);
- return messages.map((msg) => window.WWebJS.getMessageModel(msg));
- },
- {
- query,
- page: options.page,
- limit: options.limit,
- remote: options.chatId,
- }
- );
-
- return messages.map((msg) => new Message(this, msg));
- }
-
- /**
- * get all chats
- * @returns
- */
- async getChats() {
- let chats = await this.mPage.evaluate(async () => {
- return await window.WWebJS.getChats();
- });
-
- return chats.map((chat) => ChatFactory.create(this, chat));
- }
-
- /**
- * get chat by id
- * @param {*} chatId
- * @returns
- */
- async getChatById(chatId) {
- let chat = await this.mPage.evaluate(async (chatId) => {
- return await window.WWebJS.getChat(chatId);
- }, chatId);
-
- return ChatFactory.create(this, chat);
- }
-
- /**
- * group metadata
- * @param {*} chatId
- * @returns
- */
- async groupMetadata(chatId) {
- let chat = await this.mPage.evaluate(async (chatId) => {
- let chatWid = await window.Store.WidFactory.createWid(chatId);
- let chat = await window.Store.GroupMetadata.find(chatWid);
-
- return chat.serialize();
- }, chatId);
-
- if (!chat) return false;
- return chat;
- }
-
- /**
- * get all contacts
- * @returns
- */
- async getContacts() {
- let contacts = await this.mPage.evaluate(() => {
- return window.WWebJS.getContacts();
- });
-
- return contacts.map((contact) => ContactFactory.create(this, contact));
- }
-
- /**
- * save contact
- * @param {*} number
- * @returns
- */
- async saveContact(number) {
- let contact = await this.mPage.evaluate((number) => {
- return window.WWebJS.getContact(number);
- }, number);
-
- let res = ContactFactory.create(this, contact);
- return res.isMyContact;
- }
-
- /**
- * get contact by id
- * @param {*} contactId
- * @returns
- */
- async getContactById(contactId) {
- let contact = await this.mPage.evaluate((contactId) => {
- return window.WWebJS.getContact(contactId);
- }, contactId);
-
- return ContactFactory.create(this, contact);
- }
-
- /**
- * get invite info
- * @param {*} inviteCode
- * @returns
- */
- async getInviteInfo(inviteCode) {
- return await this.mPage.evaluate((inviteCode) => {
- return window.Store.InviteInfo.queryGroupInvite(inviteCode);
- }, inviteCode);
- }
-
- /**
- * accept invite
- * @param {*} inviteCode
- * @returns
- */
- async acceptInvite(inviteCode) {
- const res = await this.mPage.evaluate(async (inviteCode) => {
- return await window.Store.Invite.joinGroupViaInvite(inviteCode);
- }, inviteCode);
-
- return res.gid._serialized;
- }
-
- /**
- * set status bio
- * @param {*} status
- */
- async setStatus(status) {
- await this.mPage.evaluate(async (status) => {
- return await window.Store.StatusUtils.setMyStatus(status);
- }, status);
- }
-
- /**
- * get state
- * @returns
- */
- async getState() {
- return await this.mPage.evaluate(() => {
- if (!window.Store) return null;
- return window.Store.AppState.state;
- });
- }
-
- /**
- * Marks the client as online
- */
- async sendPresenceAvailable() {
- return await this.mPage.evaluate(() => {
- return window.Store.PresenceUtils.sendPresenceAvailable();
- });
- }
-
- /**
- * Marks the client as unavailable
- */
- async sendPresenceUnavailable() {
- return await this.mPage.evaluate(() => {
- return window.Store.PresenceUtils.sendPresenceUnavailable();
- });
- }
-
- /**
- * archive chat
- * @param {*} chatId
- * @returns
- */
- async archiveChat(chatId) {
- return await this.mPage.evaluate(async (chatId) => {
- let chat = await window.Store.Chat.get(chatId);
- await window.Store.Cmd.archiveChat(chat, true);
- return true;
- }, chatId);
- }
-
- /**
- * unarchive chat
- * @param {*} chatId
- * @returns
- */
- async unarchiveChat(chatId) {
- return await this.mPage.evaluate(async (chatId) => {
- let chat = await window.Store.Chat.get(chatId);
- await window.Store.Cmd.archiveChat(chat, false);
- return false;
- }, chatId);
- }
-
- /**
- * pin chat
- * @param {*} chatId
- * @returns
- */
- async pinChat(chatId) {
- return this.mPage.evaluate(async (chatId) => {
- let chat = window.Store.Chat.get(chatId);
- if (chat.pin) {
- return true;
- }
- const MAX_PIN_COUNT = 3;
- const chatModels = window.Store.Chat.getModelsArray();
- if (chatModels.length > MAX_PIN_COUNT) {
- let maxPinned = chatModels[MAX_PIN_COUNT - 1].pin;
- if (maxPinned) {
- return false;
- }
- }
- await window.Store.Cmd.pinChat(chat, true);
- return true;
- }, chatId);
- }
-
- /**
- * unpinchat
- * @param {*} chatId
- * @returns
- */
- async unpinChat(chatId) {
- return this.mPage.evaluate(async (chatId) => {
- let chat = window.Store.Chat.get(chatId);
- if (!chat.pin) {
- return false;
- }
- await window.Store.Cmd.pinChat(chat, false);
- return false;
- }, chatId);
- }
-
- /**
- * mute chat
- * @param {*} chatId
- * @param {*} unmuteDate
- */
- async muteChat(chatId, unmuteDate) {
- unmuteDate = unmuteDate ? unmuteDate : -1;
- await this.mPage.evaluate(
- async (chatId, timestamp) => {
- let chat = await window.Store.Chat.get(chatId);
-
- let canMute = chat.mute.canMute();
- if (!canMute) {
- throw `Can't mute this chat`;
- }
- await chat.mute.mute({
- expiration: timestamp,
- sendDevice: !0,
- });
- },
- chatId,
- unmuteDate || -1
- );
- }
-
- /**
- * unmute chat
- * @param {*} chatId
- */
- async unmuteChat(chatId) {
- await this.mPage.evaluate(async (chatId) => {
- let chat = await window.Store.Chat.get(chatId);
- await window.Store.Cmd.muteChat(chat, false);
- }, chatId);
- }
-
- /**
- * setting ephemeral message
- * @param {*} chatId
- * @param {*} ephemeralDuration
- */
- async setEphemeral(chatId, ephemeralDuration) {
- ephemeralDuration = ephemeralDuration ? ephemeralDuration : 0;
- await this.mPage.evaluate(
- async (chatId, ephemeralDuration) => {
- const chat = window.Store.Chat.get(chatId);
-
- if (chat.isGroup) {
- return await window.WWebJS.group.setProperty(
- chat.id,
- "ephemeral",
- ephemeralDuration
- );
- }
+ if (contact.length == 0) return null;
+ return contact;
+ }
- return await window.Store.ChangeEphemeralDuration(
- chat,
- ephemeralDuration
- ).catch((e) => e);
- },
- chatId,
- ephemeralDuration
- );
- }
-
- /**
- * mark chat unread
- * @param {*} chatId
- */
- async markChatUnread(chatId) {
- await this.mPage.evaluate(async (chatId) => {
- let chat = await window.Store.Chat.get(chatId);
- await window.Store.Cmd.markChatUnread(chat, true);
- }, chatId);
- }
-
- /**
- * get profile picture
- * @param {*} contactId
- * @returns
- */
- async getProfilePict(contactId) {
- const profilePic = await this.mPage.evaluate(async (contactId) => {
- try {
- const chatWid = window.Store.WidFactory.createWid(contactId);
- return await window.Store.ProfilePic.profilePicFind(chatWid);
- } catch (err) {
- if (err.name === "ServerStatusCodeError") return undefined;
- throw err;
- }
- }, contactId);
-
- return profilePic ? profilePic.eurl : undefined;
- }
-
- /**
- * Gets the Contact's common groups with you. Returns empty array if you don't have any common group.
- * @param {string} contactId the whatsapp user's ID (_serialized format)
- * @returns {Promise}
- */
- async getCommonGroups(contactId) {
- const commonGroups = await this.mPage.evaluate(async (contactId) => {
- let contact = window.Store.Contact.get(contactId);
- if (!contact) {
- const wid = window.Store.WidFactory.createUserWid(contactId);
- const chatConstructor = window.Store.Contact.getModelsArray().find(
- (c) => !c.isGroup
- ).constructor;
- contact = new chatConstructor({
- id: wid,
- });
- }
-
- if (contact.commonGroups) {
- return contact.commonGroups.serialize();
- }
- const status = await window.Store.findCommonGroups(contact);
- if (status) {
- return contact.commonGroups.serialize();
- }
- return [];
- }, contactId);
- const chats = [];
- for (const group of commonGroups) {
- chats.push(group.id);
- }
- return chats;
- }
-
- /**
- * Force reset of connection state for the client
- */
- async resetState() {
- await this.mPage.evaluate(() => {
- window.Store.AppState.phoneWatchdog.shiftTimer.forceRunNow();
- });
- }
-
- /**
- * Check if a given ID is registered in whatsapp
- * @param {string} id the whatsapp user's ID
- * @returns {Promise}
- */
- async isRegisteredUser(id) {
- return Boolean(await this.getNumberId(id));
- }
-
- /**
- * Get the registered WhatsApp ID for a number.
- * Will return null if the number is not registered on WhatsApp.
- * @param {string} number Number or ID ("@c.us" will be automatically appended if not specified)
- * @returns {Promise}
- */
- async getNumberId(number) {
- if (!number.endsWith("@c.us")) {
- number += "@c.us";
- }
-
- return await this.mPage.evaluate(async (number) => {
- const wid = window.Store.WidFactory.createWid(number);
- const result = await window.Store.QueryExist(wid);
- if (!result || result.wid === undefined) return null;
- return result.wid;
- }, number);
- }
-
- /**
- * Get the formatted number of a WhatsApp ID.
- * @param {string} number Number or ID
- * @returns {Promise}
- */
- async getFormattedNumber(number) {
- if (!number.endsWith("@s.whatsapp.net"))
- number = number.replace("c.us", "s.whatsapp.net");
- if (!number.includes("@s.whatsapp.net"))
- number = `${number}@s.whatsapp.net`;
-
- return await this.mPage.evaluate(async (numberId) => {
- return window.Store.NumberInfo.formattedPhoneNumber(numberId);
- }, number);
- }
-
- /**
- * Get the country code of a WhatsApp ID.
- * @param {string} number Number or ID
- * @returns {Promise}
- */
- async getCountryCode(number) {
- number = number.replace(" ", "").replace("+", "").replace("@c.us", "");
-
- return await this.mPage.evaluate(async (numberId) => {
- return window.Store.NumberInfo.findCC(numberId);
- }, number);
- }
-
- /**
- * Create a new group
- * @param {string} name group title
- * @param {Array} participants an array of Contacts or contact IDs to add to the group
- * @returns {Object} createRes
- * @returns {string} createRes.gid - ID for the group that was just created
- * @returns {Object.} createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user's privacy settings don't allow you to add them to groups.
- */
- async createGroup(name, participants) {
- if (!Array.isArray(participants) || participants.length == 0) {
- throw "You need to add at least one other participant to the group";
- }
-
- if (participants.every((c) => c instanceof Contact)) {
- participants = participants.map((c) => c.id._serialized);
- }
-
- const createRes = await this.mPage.evaluate(
- async (name, participantIds) => {
- const participantWIDs = participantIds.map((p) =>
- window.Store.WidFactory.createWid(p)
- );
- return await window.Store.GroupUtils.createGroup(
- name,
- participantWIDs,
- 0
+ /**
+ *
+ * @param {*} chatId
+ * @param {*} name
+ * @param {*} choices
+ * @param {*} options
+ * @returns
+ */
+ async sendPoll(chatId, name, choices, options = {}) {
+ let message = await this.mPage.evaluate(
+ async ({
+ chatId,
+ name,
+ choices,
+ options
+ }) => {
+ let rawMessage = {
+ waitForAck: true,
+ sendSeen: true,
+ type: "poll_creation",
+ pollName: name,
+ pollOptions: choices.map((name, localId) => ({
+ name,
+ localId,
+ })),
+ pollEncKey: self.crypto.getRandomValues(new Uint8Array(32)),
+ pollSelectableOptionsCount: options.selectableCount || 0,
+ messageSecret: self.crypto.getRandomValues(new Uint8Array(32)),
+ };
+
+ await window.WWebJS.sendRawMessage(chatId, rawMessage, options);
+ }, {
+ chatId,
+ name,
+ choices,
+ options,
+ }
);
- },
- name,
- participants
- );
-
- const missingParticipants = createRes.participants.reduce((missing, c) => {
- const id = c.wid._serialized;
- const statusCode = c.error ? c.error.toString() : "200";
- if (statusCode != 200)
- return Object.assign(missing, {
- [id]: statusCode,
- });
- return missing;
- }, {});
-
- return {
- gid: createRes.wid,
- missingParticipants,
- };
- }
-
- /**
- * Get all current Labels
- * @returns {Promise>}
- */
- async getLabels() {
- const labels = await this.mPage.evaluate(async () => {
- return window.WWebJS.getLabels();
- });
-
- return labels.map((data) => new Label(this, data));
- }
-
- /**
- * Get Label instance by ID
- * @param {string} labelId
- * @returns {Promise}
- */
- async getLabelById(labelId) {
- const label = await this.mPage.evaluate(async (labelId) => {
- return window.WWebJS.getLabel(labelId);
- }, labelId);
-
- return new Label(this, label);
- }
-
- /**
- * Get all Labels assigned to a chat
- * @param {string} chatId
- * @returns {Promise>}
- */
- async getChatLabels(chatId) {
- const labels = await this.mPage.evaluate(async (chatId) => {
- return window.WWebJS.getChatLabels(chatId);
- }, chatId);
-
- return labels.map((data) => new Label(this, data));
- }
-
- /**
- * Get all Chats for a specific Label
- * @param {string} labelId
- * @returns {Promise>}
- */
- async getChatsByLabelId(labelId) {
- const chatIds = await this.mPage.evaluate(async (labelId) => {
- const label = window.Store.Label.get(labelId);
- const labelItems = label.labelItemCollection.getModelsArray();
- return labelItems.reduce((result, item) => {
- if (item.parentType === "Chat") {
- result.push(item.parentId);
+
+ if (!message) return null;
+ return new Message(this, message);
+ }
+
+ /**
+ * clear all messages
+ */
+ async clearAllMsg() {
+ function _0x178e() {
+ const _0x59fc13 = ['4807125ZaiQmb', '80340jovByq', '923210mDLQBS', 'filter', '4942026OFdCiY', '8EDdczY', 'isGroup', '_serialized', 'groupMetadata', '2260726nUvkes', '476xxLSsp', 'clearMessage', '29343MVIOjf', 'map', 'length', 'getChats', '9078503wlTApE'];
+ _0x178e = function () {
+ return _0x59fc13;
+ };
+ return _0x178e();
}
- return result;
- }, []);
- }, labelId);
-
- return Promise.all(chatIds.map((id) => this.getChatById(id)));
- }
-
- /**
- * Gets all blocked contacts by host account
- * @returns {Promise>}
- */
- async getBlockedContacts() {
- const blockedContacts = await this.mPage.evaluate(() => {
- let chatIds = window.Store.Blocklist.getModelsArray().map(
- (a) => a.id._serialized
- );
- return Promise.all(chatIds.map((id) => window.WWebJS.getContact(id)));
- });
-
- return blockedContacts.map((contact) =>
- ContactFactory.create(this.client, contact)
- );
- }
-
- /**
- * Sets the current user's profile picture.
- * @param {MessageMedia} media
- * @returns {Promise} Returns true if the picture was properly updated.
- */
- async setProfilePicture(media, type = "normal") {
- const success = await this.mPage.evaluate(
- ({ chatid, media, type }) => {
- return window.WWebJS.setPicture(chatid, media, type);
- },
- {
- chatId: this.info.wid._serialized,
- media,
- type,
- }
- );
-
- return success;
- }
-
- /**
- * Deletes the current user's profile picture.
- * @returns {Promise} Returns true if the picture was properly deleted.
- */
- async deleteProfilePicture() {
- const success = await this.mPage.evaluate((chatid) => {
- return window.WWebJS.deletePicture(chatid);
- }, this.info.wid._serialized);
-
- return success;
- }
-
- /**
- *
- * @param {string} chatId
- * @returns {Promise}
- */
- async getLastSeen(chatId) {
- const chat = await this.mPage.evaluate(async (chatId) => {
- return (
- (await window.WWebJS.chat.getLastSeen(chatId)) ||
- (await window.WWebJS.getChatOnline(chatId))
- );
- }, chatId);
-
- if (!chat) return false;
- return Number(chat) > 2 ? Number(chat) : "online";
- }
-
- /*
-#####################################
-# NEW FUNCTION#
-#####################################
-*/
-
- /**
- * get detail host
- * @returns
- */
- getHost() {
- return this.mPage.evaluate(() => {
- return WPP.whatsapp.Conn.attributes;
- });
- }
-
- /**
- * setting theme dark or light
- * @param {*} type
- * @returns
- */
- async setTheme(type = "dark") {
- if (type !== "dark" && type !== "light") {
- return {
- status: false,
- message: 'Invalid option. Only "dark" or "light" are allowed',
- };
- }
-
- try {
- await this.mPage.evaluate(async (type) => {
- await window.extra.theme[0].setTheme(type);
- }, type);
-
- return {
- status: 200,
- message: `Successfully changed to ${type} mode`,
- };
- } catch (error) {
- return {
- status: false,
- message: "Can't change theme",
- };
- }
- }
-
- /**
- * get theme waweb
- * @returns
- */
- async getTheme() {
- const theme = await this.mPage.evaluate(async () => {
- if (window.localStorage) {
- return await JSON.parse(JSON.stringify(window.localStorage))?.theme;
- } else {
- return await window.Store.Theme.getTheme();
- }
- });
-
- if (!theme) return false;
- return theme;
- }
-
- /**
- * join wa beta
- * @param {*} act
- * @returns
- */
- async joinBeta(act) {
- const res = await this.mPage.evaluate((act) => {
- return window.extra.joinBeta(act);
- }, act);
- if (act == true) {
- return `successfully entered beta mode`;
- } else if (act == false) {
- return `managed to get out of beta mode`;
- }
- }
-
- /**
- * get member request
- * @param {*} jid
- * @returns
- */
- async getMemberRequest(jid) {
- const res = await this.mPage.evaluate(async (jid) => {
- return window.extra.group.memberRequest(jid);
- }, jid);
- return res;
- }
-
- /**
- * approve member
- * @param {*} jid
- * @param {*} to
- * @returns
- */
- async approveRequest(jid, to) {
- const res = await this.mPage.evaluate(
- ({ jid, to }) => {
- return window.extra.group.approve(jid, to);
- },
- {
- jid,
- to,
- }
- );
- return res;
- }
-
- /**
- * reject member
- * @param {*} jid
- * @param {*} to
- */
- async rejectRequest(jid, to) {
- const res = await this.mPage.evaluate(
- ({ jid, to }) => {
- return window.extra.group.reject(jid, to);
- },
- {
- jid,
- to,
- }
- );
- }
-
- /**
- * send call
- * @param {*} chatId
- * @param {*} options
- * @returns
- */
- async sendCall(chatId, options = {}) {
- if (!Array.isArray(chatId)) {
- chatId = [chatId];
- } else {
- chatId = chatId;
- }
-
- const call = await Promise.all(
- chatId.map(async (id) => {
- return await this.mPage.evaluate(
- ({ id, options }) => {
- return window.WWebJS.call.offer(id, options);
- },
- {
- id,
- options,
- }
- );
- })
- );
-
- return chatId.length;
- }
-
- /**
- * end calling
- * @param {*} chatId
- * @returns
- */
- async endCall(chatId) {
- const end = await this.mPage.evaluate((chatId) => {
- return window.WWebJS.call.end(chatId);
- }, chatId);
-
- if (!end) return false;
- return true;
- }
-
- /**
- * accept call
- * @param {*} chatId
- * @returns
- */
- async acceptCall(chatId) {
- const end = await this.mPage.evaluate((chatId) => {
- return window.WWebJS.call.accept(chatId);
- }, chatId);
-
- if (!end) return false;
- return true;
- }
-
- /**
- * send story text
- * @param {*} text
- * @param {*} bg
- * @param {*} fonts
- * @returns
- */
- async sendStoryText(text, bg, fonts) {
- if (!text) return "Input story text";
- if (!bg) return "Input background color (hex)";
- if (!fonts) return "Input style font (number)";
- try {
- const res = await this.mPage.evaluate(
- async ({ text, bg, fonts }) => {
- return window.extra.status.text(text, {
- backgroundColor: bg,
- font: fonts,
- });
- },
- {
- text,
- bg,
- fonts,
+ const _0x2886d2 = _0xe390;
+ (function (_0x1f8679, _0x57e58c) {
+ const _0x42f211 = _0xe390,
+ _0x566cfc = _0x1f8679();
+ while (!![]) {
+ try {
+ const _0x275681 = parseInt(_0x42f211(0x139)) / 0x1 + -parseInt(_0x42f211(0x140)) / 0x2 + parseInt(_0x42f211(0x143)) / 0x3 * (parseInt(_0x42f211(0x141)) / 0x4) + -parseInt(_0x42f211(0x137)) / 0x5 + parseInt(_0x42f211(0x138)) / 0x6 + parseInt(_0x42f211(0x136)) / 0x7 * (parseInt(_0x42f211(0x13c)) / 0x8) + -parseInt(_0x42f211(0x13b)) / 0x9;
+ if (_0x275681 === _0x57e58c) break;
+ else _0x566cfc['push'](_0x566cfc['shift']());
+ } catch (_0x1a8072) {
+ _0x566cfc['push'](_0x566cfc['shift']());
+ }
+ }
+ }(_0x178e, 0xb8b56));
+
+ function _0xe390(_0x2c9b7f, _0xa8fc3c) {
+ const _0x178e68 = _0x178e();
+ return _0xe390 = function (_0xe3905d, _0x579f17) {
+ _0xe3905d = _0xe3905d - 0x135;
+ let _0x454ad9 = _0x178e68[_0xe3905d];
+ return _0x454ad9;
+ }, _0xe390(_0x2c9b7f, _0xa8fc3c);
}
- );
- return "Successfully sent status text to WhatsApp";
- } catch (error) {
- return "Failed to send status text to WhatsApp";
- }
- }
-
- /**
- * get name whatsapp
- * @param {*} jid
- * @returns
- */
- async getName(jid) {
- const contact = await this.getContactById(jid);
- return (
- contact.name || contact.pushname || contact.shortName || contact.number
- );
- }
-
- /**
- * send file
- * @param {sen} chatId
- * @param {*} pathOrBase64
- * @param {*} nameOrOptions
- */
- async sendFile(chatId, pathOrBase64, nameOrOptions) {
- if (typeof nameOrOptions === "string") {
- options.filename = nameOrOptions;
- nameOrOptions = {};
- }
-
- const fileContent = Util.getFile(pathOrBase64);
- var view = nameOrOptions.view ? true : false;
- var ptt = nameOrOptions.ptt ? true : false;
- let options = {
- type: nameOrOptions?.type ? nameOrOptions.type : "auto-detect",
- filename: nameOrOptions?.filename ? nameOrOptions.filename : "",
- mimetype: fileContent.mime,
- isViewOnce: view,
- isPtt: ptt,
- };
-
- const base64 = `data:${(await fileContent).mime};base64,${(
- await fileContent
- ).data.toString("base64")}`;
-
- if (!!nameOrOptions?.quoted) {
- options.quotedMsg =
- typeof nameOrOptions.quoted === "object"
- ? nameOrOptions.quoted.id._serialized
- : nameOrOptions.quoted || nameOrOptions.quoted._serialized;
-
- delete nameOrOptions.quoted;
- }
-
- if (nameOrOptions?.mentions) {
- options.mentionedJidList = Array.isArray(options.mentions)
- ? options.mentions.map((contact) =>
- contact?.id ? contact?.id?._serialized : contact
- )
- : [];
-
- delete nameOrOptions.mentions;
- }
-
- options = {
- ...nameOrOptions,
- ...options,
- };
-
- const msg = await this.mPage.evaluate(
- async ({ chatId, base64, options }) => {
- return WPP.chat.sendFileMessage(chatId, base64, options);
- },
- {
- chatId,
- base64,
- options,
- }
- );
- }
-
- /**
- * clear message
- * @param {*} chatId
- * @returns
- */
- async clearMessage(chatId) {
- return this.mPage.evaluate((chatId) => {
- return window.WWebJS.sendClearChat(chatId);
- }, chatId);
- }
-
- /**
- * send read status
- * @param {*} chatId
- * @param {*} statusId
- */
- async sendReadStatus(chatId, statusId) {
- await this.mPage.evaluate(
- async ({ chatId, statusId }) => {
- const wid = window.Store.WidFactory.createWid(chatId);
- const statusStore = window.Store.StatusV3.get(wid);
-
- const status = statusStore?.msgs.get(statusId);
- await statusStore?.sendReadStatus(
- status,
- status?.mediaKeyTimestamp || status?.t
- );
- },
- {
- chatId,
- statusId,
- }
- );
- }
-
- /**
- * get story
- * @param {*} chatId
- * @returns
- */
- async getStories(chatId = this.info.wid._serialized) {
- const message = await this.mPage.evaluate((chatId) => {
- if (chatId === "all") {
- const status = window.Store.StatusV3.getModelsArray();
-
- if (!status) return undefined;
- return status.map((a) => a.serialize());
- } else {
- const Wid = window.Store.WidFactory.createWid(chatId);
- const status = window.Store.StatusV3.get(Wid);
-
- if (!status) return new Error("No Status Found!");
- const msg = status.serialize();
- return [msg];
- }
- }, chatId);
-
- if (!message === undefined) return undefined;
- return message;
- }
-
- /**
- * get contact by name
- * @param {*} name
- * @returns
- */
- async getContactByName(name) {
- let contact = (await this.getContacts()).filter(
- (a) =>
- a.name && (a.name.toLowerCase().includes(name) || a.name.includes(name))
- );
-
- if (contact.length == 0) return null;
- return contact;
- }
-
- /**
- *
- * @param {*} chatId
- * @param {*} name
- * @param {*} choices
- * @param {*} options
- * @returns
- */
- async sendPoll(chatId, name, choices, options = {}) {
- let message = await this.mPage.evaluate(
- async ({ chatId, name, choices, options }) => {
- let rawMessage = {
- waitForAck: true,
- sendSeen: true,
- type: "poll_creation",
- pollName: name,
- pollOptions: choices.map((name, localId) => ({
- name,
- localId,
- })),
- pollEncKey: self.crypto.getRandomValues(new Uint8Array(32)),
- pollSelectableOptionsCount: options.selectableCount || 0,
- messageSecret: self.crypto.getRandomValues(new Uint8Array(32)),
- };
+ const data = await this[_0x2886d2(0x135)](),
+ groupSerializedArray = data[_0x2886d2(0x13a)](_0x17615e => _0x17615e[_0x2886d2(0x13d)])[_0x2886d2(0x144)](_0x3f97aa => _0x3f97aa[_0x2886d2(0x13f)]['id'][_0x2886d2(0x13e)]),
+ privateSerializedArray = data[_0x2886d2(0x13a)](_0x14f289 => !_0x14f289[_0x2886d2(0x13d)])[_0x2886d2(0x144)](_0x1385d1 => _0x1385d1['id'][_0x2886d2(0x13e)]),
+ allSerializedArray = [...groupSerializedArray, ...privateSerializedArray];
+ for (let i = 0x0; i < allSerializedArray[_0x2886d2(0x145)]; i++) {
+ const id = allSerializedArray[i];
+ this[_0x2886d2(0x142)](id);
+ }
+ }
- await window.WWebJS.sendRawMessage(chatId, rawMessage, options);
- },
- {
- chatId,
- name,
- choices,
- options,
- }
- );
-
- if (!message) return null;
- return new Message(this, message);
- }
-
- /**
- * clear all messages
- */
- async clearAllMsg() {
- function _0x178e() {
- const _0x59fc13 = [
- "4807125ZaiQmb",
- "80340jovByq",
- "923210mDLQBS",
- "filter",
- "4942026OFdCiY",
- "8EDdczY",
- "isGroup",
- "_serialized",
- "groupMetadata",
- "2260726nUvkes",
- "476xxLSsp",
- "clearMessage",
- "29343MVIOjf",
- "map",
- "length",
- "getChats",
- "9078503wlTApE",
- ];
- _0x178e = function () {
- return _0x59fc13;
- };
- return _0x178e();
- }
- const _0x2886d2 = _0xe390;
- (function (_0x1f8679, _0x57e58c) {
- const _0x42f211 = _0xe390,
- _0x566cfc = _0x1f8679();
- while (!![]) {
- try {
- const _0x275681 =
- parseInt(_0x42f211(0x139)) / 0x1 +
- -parseInt(_0x42f211(0x140)) / 0x2 +
- (parseInt(_0x42f211(0x143)) / 0x3) *
- (parseInt(_0x42f211(0x141)) / 0x4) +
- -parseInt(_0x42f211(0x137)) / 0x5 +
- parseInt(_0x42f211(0x138)) / 0x6 +
- (parseInt(_0x42f211(0x136)) / 0x7) *
- (parseInt(_0x42f211(0x13c)) / 0x8) +
- -parseInt(_0x42f211(0x13b)) / 0x9;
- if (_0x275681 === _0x57e58c) break;
- else _0x566cfc["push"](_0x566cfc["shift"]());
- } catch (_0x1a8072) {
- _0x566cfc["push"](_0x566cfc["shift"]());
+ /**
+ * screenshot whatsapp
+ * @returns
+ */
+ async myPage() {
+ await this.mPage.setViewportSize({
+ width: 961,
+ height: 2000
+ })
+ let media = await this.mPage.screenshot()
+ let upload = await Util.upload(media)
+ return upload.url
+ }
+
+ /**
+ * screenshot custom site
+ * @param {*} url
+ * @returns
+ */
+ async screenPage(url) {
+ function _0x4f70(_0x156778, _0x3e4092) {
+ const _0x3cc1e5 = _0x3cc1();
+ return _0x4f70 = function (_0x4f70b7, _0x2a78f8) {
+ _0x4f70b7 = _0x4f70b7 - 0xcb;
+ let _0x5ce2de = _0x3cc1e5[_0x4f70b7];
+ return _0x5ce2de;
+ }, _0x4f70(_0x156778, _0x3e4092);
}
- }
- })(_0x178e, 0xb8b56);
-
- function _0xe390(_0x2c9b7f, _0xa8fc3c) {
- const _0x178e68 = _0x178e();
- return (
- (_0xe390 = function (_0xe3905d, _0x579f17) {
- _0xe3905d = _0xe3905d - 0x135;
- let _0x454ad9 = _0x178e68[_0xe3905d];
- return _0x454ad9;
- }),
- _0xe390(_0x2c9b7f, _0xa8fc3c)
- );
- }
- const data = await this[_0x2886d2(0x135)](),
- groupSerializedArray = data[_0x2886d2(0x13a)](
- (_0x17615e) => _0x17615e[_0x2886d2(0x13d)]
- )[_0x2886d2(0x144)](
- (_0x3f97aa) => _0x3f97aa[_0x2886d2(0x13f)]["id"][_0x2886d2(0x13e)]
- ),
- privateSerializedArray = data[_0x2886d2(0x13a)](
- (_0x14f289) => !_0x14f289[_0x2886d2(0x13d)]
- )[_0x2886d2(0x144)]((_0x1385d1) => _0x1385d1["id"][_0x2886d2(0x13e)]),
- allSerializedArray = [...groupSerializedArray, ...privateSerializedArray];
- for (let i = 0x0; i < allSerializedArray[_0x2886d2(0x145)]; i++) {
- const id = allSerializedArray[i];
- this[_0x2886d2(0x142)](id);
- }
- }
-
- /**
- * screenshot whatsapp
- * @returns
- */
- async myPage() {
- await this.mPage.setViewportSize({
- width: 961,
- height: 2000,
- });
- let media = await this.mPage.screenshot();
- let upload = await Util.upload(media);
- return upload.url;
- }
-
- /**
- * screenshot custom site
- * @param {*} url
- * @returns
- */
- async screenPage(url) {
- function _0x4f70(_0x156778, _0x3e4092) {
- const _0x3cc1e5 = _0x3cc1();
- return (
- (_0x4f70 = function (_0x4f70b7, _0x2a78f8) {
- _0x4f70b7 = _0x4f70b7 - 0xcb;
- let _0x5ce2de = _0x3cc1e5[_0x4f70b7];
- return _0x5ce2de;
- }),
- _0x4f70(_0x156778, _0x3e4092)
- );
- }
- const _0x20ef5e = _0x4f70;
- (function (_0x4ddcb3, _0x1186b6) {
- const _0x40925f = _0x4f70,
- _0x65b0ad = _0x4ddcb3();
- while (!![]) {
+ const _0x20ef5e = _0x4f70;
+ (function (_0x4ddcb3, _0x1186b6) {
+ const _0x40925f = _0x4f70,
+ _0x65b0ad = _0x4ddcb3();
+ while (!![]) {
+ try {
+ const _0x282c38 = -parseInt(_0x40925f(0xd7)) / 0x1 * (-parseInt(_0x40925f(0xed)) / 0x2) + -parseInt(_0x40925f(0xeb)) / 0x3 * (-parseInt(_0x40925f(0xec)) / 0x4) + -parseInt(_0x40925f(0xcc)) / 0x5 * (-parseInt(_0x40925f(0xd1)) / 0x6) + -parseInt(_0x40925f(0xce)) / 0x7 * (-parseInt(_0x40925f(0xe8)) / 0x8) + -parseInt(_0x40925f(0xd5)) / 0x9 + parseInt(_0x40925f(0xd6)) / 0xa + -parseInt(_0x40925f(0xd4)) / 0xb;
+ if (_0x282c38 === _0x1186b6) break;
+ else _0x65b0ad['push'](_0x65b0ad['shift']());
+ } catch (_0x508f08) {
+ _0x65b0ad['push'](_0x65b0ad['shift']());
+ }
+ }
+ }(_0x3cc1, 0xe77fb));
+ if (!/https?:\/\//i['test'](url)) return _0x20ef5e(0xd2);
+ const browsers = await playwright['chromium'][_0x20ef5e(0xe6)]({
+ 'headless': !![],
+ 'args': [_0x20ef5e(0xe7), _0x20ef5e(0xe1), _0x20ef5e(0xd9), _0x20ef5e(0xcd), _0x20ef5e(0xe4), _0x20ef5e(0xe9), _0x20ef5e(0xe5)]
+ });
try {
- const _0x282c38 =
- (-parseInt(_0x40925f(0xd7)) / 0x1) *
- (-parseInt(_0x40925f(0xed)) / 0x2) +
- (-parseInt(_0x40925f(0xeb)) / 0x3) *
- (-parseInt(_0x40925f(0xec)) / 0x4) +
- (-parseInt(_0x40925f(0xcc)) / 0x5) *
- (-parseInt(_0x40925f(0xd1)) / 0x6) +
- (-parseInt(_0x40925f(0xce)) / 0x7) *
- (-parseInt(_0x40925f(0xe8)) / 0x8) +
- -parseInt(_0x40925f(0xd5)) / 0x9 +
- parseInt(_0x40925f(0xd6)) / 0xa +
- -parseInt(_0x40925f(0xd4)) / 0xb;
- if (_0x282c38 === _0x1186b6) break;
- else _0x65b0ad["push"](_0x65b0ad["shift"]());
- } catch (_0x508f08) {
- _0x65b0ad["push"](_0x65b0ad["shift"]());
+ const context = await browsers['newContext']({
+ .../phone|hp/i['test'](url[_0x20ef5e(0xe3)]()) ? playwright[_0x20ef5e(0xdf)][_0x20ef5e(0xd8)] : playwright[_0x20ef5e(0xdf)][_0x20ef5e(0xea)],
+ 'bypassCSP': !![],
+ 'ignoreHTTPSErrors': !![],
+ 'colorScheme': _0x20ef5e(0xdc)
+ }),
+ pages = await context[_0x20ef5e(0xcb)]();
+ await pages['goto'](Util[_0x20ef5e(0xe0)](url)[0x0], {
+ 'waitUntil': _0x20ef5e(0xe2),
+ 'timeout': 0x0
+ }), /full/i[_0x20ef5e(0xcf)](url) ? await pages['waitForLoadState'](_0x20ef5e(0xe2)) : await pages[_0x20ef5e(0xda)]('load');
+ let media = await pages[_0x20ef5e(0xde)]({
+ 'fullPage': /full/i[_0x20ef5e(0xcf)](url) ? !![] : ![],
+ 'type': _0x20ef5e(0xd3)
+ }),
+ upload = await Util[_0x20ef5e(0xdb)](media);
+ return upload[_0x20ef5e(0xd0)];
+ await browsers[_0x20ef5e(0xdd)]();
+ } catch (_0x449e3e) {
+ return _0x449e3e;
+ await browsers[_0x20ef5e(0xdd)]();
+ }
+
+ function _0x3cc1() {
+ const _0x24cabc = ['2919410SfFZqZ', '47JCTFSv', 'iPhone\x2013\x20Pro\x20Max', '--no-default-browser-check', 'waitForLoadState', 'upload', 'dark', 'close', 'screenshot', 'devices', 'isUrl', '--no-first-run', 'networkidle', 'toLowerCase', '--disable-accelerated-2d-canvas', '--start-maximied', 'launch', '--no-sandbox', '16pjwdOs', '--disable-session-crashed-bubble', 'Desktop\x20Chrome', '3935733dcUdcQ', '4kDSZiI', '80644ATgteP', 'newPage', '25OwRBXP', '--disable-setuid-sandbox', '4337683WuIQYr', 'test', 'url', '1431348eJQgVd', 'Please\x20start\x20with\x20http\x20or\x20https', 'png', '44575201hXGbcy', '8375436ufMYbO'];
+ _0x3cc1 = function () {
+ return _0x24cabc;
+ };
+ return _0x3cc1();
}
- }
- })(_0x3cc1, 0xe77fb);
- if (!/https?:\/\//i["test"](url)) return _0x20ef5e(0xd2);
- const browsers = await playwright["chromium"][_0x20ef5e(0xe6)]({
- headless: !![],
- args: [
- _0x20ef5e(0xe7),
- _0x20ef5e(0xe1),
- _0x20ef5e(0xd9),
- _0x20ef5e(0xcd),
- _0x20ef5e(0xe4),
- _0x20ef5e(0xe9),
- _0x20ef5e(0xe5),
- ],
- });
- try {
- const context = await browsers["newContext"]({
- ...(/phone|hp/i["test"](url[_0x20ef5e(0xe3)]())
- ? playwright[_0x20ef5e(0xdf)][_0x20ef5e(0xd8)]
- : playwright[_0x20ef5e(0xdf)][_0x20ef5e(0xea)]),
- bypassCSP: !![],
- ignoreHTTPSErrors: !![],
- colorScheme: _0x20ef5e(0xdc),
- }),
- pages = await context[_0x20ef5e(0xcb)]();
- await pages["goto"](Util[_0x20ef5e(0xe0)](url)[0x0], {
- waitUntil: _0x20ef5e(0xe2),
- timeout: 0x0,
- }),
- /full/i[_0x20ef5e(0xcf)](url)
- ? await pages["waitForLoadState"](_0x20ef5e(0xe2))
- : await pages[_0x20ef5e(0xda)]("load");
- let media = await pages[_0x20ef5e(0xde)]({
- fullPage: /full/i[_0x20ef5e(0xcf)](url) ? !![] : ![],
- type: _0x20ef5e(0xd3),
- }),
- upload = await Util[_0x20ef5e(0xdb)](media);
- return upload[_0x20ef5e(0xd0)];
- await browsers[_0x20ef5e(0xdd)]();
- } catch (_0x449e3e) {
- return _0x449e3e;
- await browsers[_0x20ef5e(0xdd)]();
- }
-
- function _0x3cc1() {
- const _0x24cabc = [
- "2919410SfFZqZ",
- "47JCTFSv",
- "iPhone\x2013\x20Pro\x20Max",
- "--no-default-browser-check",
- "waitForLoadState",
- "upload",
- "dark",
- "close",
- "screenshot",
- "devices",
- "isUrl",
- "--no-first-run",
- "networkidle",
- "toLowerCase",
- "--disable-accelerated-2d-canvas",
- "--start-maximied",
- "launch",
- "--no-sandbox",
- "16pjwdOs",
- "--disable-session-crashed-bubble",
- "Desktop\x20Chrome",
- "3935733dcUdcQ",
- "4kDSZiI",
- "80644ATgteP",
- "newPage",
- "25OwRBXP",
- "--disable-setuid-sandbox",
- "4337683WuIQYr",
- "test",
- "url",
- "1431348eJQgVd",
- "Please\x20start\x20with\x20http\x20or\x20https",
- "png",
- "44575201hXGbcy",
- "8375436ufMYbO",
- ];
- _0x3cc1 = function () {
- return _0x24cabc;
- };
- return _0x3cc1();
- }
- }
-
- async getUploadLimits(messageType) {
- const uploadLimit = await this.mPage.evaluate(async (messageType) => {
- return await window.WWebJS.getUploadLimits(messageType);
- }, messageType);
-
- return uploadLimit;
- }
-
- async editMessage(msg, content) {
- const msgid = msg.id._serialized;
- return await this.mPage.evaluate(
- ({ msgid, content }) => {
- WPP.chat.editMessage(msgid, content);
- },
- {
- msgid,
- content,
- }
- );
- }
-
- async forward(chatId, msgId, options = {}) {
- if (!msgId) throw new Error("No Input Message ID");
- if (!chatId) throw new Error("No Input Chat ID");
-
- await this.mPage.evaluate(
- async ({ msgId, chatId }) => {
- return WPP.chat.forwardMessage(chatId, msgId);
- },
- {
- msgId,
- chatId,
- }
- );
- }
-
- async sendRawMessage(jid, content) {
- await this.mPage.evaluate(
- ({ jid, content }) => {
- return window.WWebJS.sendRawMessage(jid, content);
- },
- {
- jid,
- content,
- }
- );
- }
+ }
+
+
+ async getUploadLimits(messageType) {
+ const uploadLimit = await this.mPage.evaluate(async (messageType) => {
+ return await window.WWebJS.getUploadLimits(messageType);
+ }, messageType);
+
+ return uploadLimit;
+ }
+
+ async editMessage(msg, content) {
+ const msgid = msg.id._serialized
+ return await this.mPage.evaluate(({
+ msgid,
+ content
+ }) => {
+ WPP.chat.editMessage(msgid, content)
+ }, {
+ msgid,
+ content
+ })
+ }
+
+ async forward(chatId, msgId, options = {}) {
+ if (!msgId) throw new Error("No Input Message ID")
+ if (!chatId) throw new Error("No Input Chat ID")
+
+ await this.mPage.evaluate(async ({
+ msgId,
+ chatId
+ }) => {
+ return WPP.chat.forwardMessage(chatId, msgId)
+ }, {
+ msgId,
+ chatId
+ })
+ }
+
+ async createChannel(name, desc, pict) {
+ await this.mPage.evaluate(({ name, desc, pict }) => {
+ return WPP.newsletter.create(name, {
+ description: desc,
+ picture: pict
+ })
+ }, { name, desc, pict })
+ }
+
+ /* New Function */
+ async parseMention(text) {
+ return [...text.matchAll(/@([0-9]{5,16}|0)/g)].map(v => v[1] + '@c.us') || []
+ }
+
}
-export default Client;
+module.exports = Client;
diff --git a/src/LinkingMethod.js b/src/LinkingMethod.js
index b4bd8e3..f561594 100644
--- a/src/LinkingMethod.js
+++ b/src/LinkingMethod.js
@@ -1,12 +1,3 @@
-/*
- * MywaJS 2023
- * re-developed wwebjs
- * using with playwright & wajs
- * contact:
- * wa: 085157489446
- * ig: amirul.dev
- */
-
class LinkingMethod {
/**
*
@@ -52,4 +43,4 @@ class LinkingMethod {
}
}
-export default LinkingMethod;
\ No newline at end of file
+module.exports = LinkingMethod;
\ No newline at end of file
diff --git a/src/authStrategies/BaseAuthStrategy.js b/src/authStrategies/BaseAuthStrategy.js
index e4b32af..ffba52c 100644
--- a/src/authStrategies/BaseAuthStrategy.js
+++ b/src/authStrategies/BaseAuthStrategy.js
@@ -33,4 +33,4 @@ class BaseAuthStrategy {
async logout() { }
}
-export default BaseAuthStrategy;
\ No newline at end of file
+module.exports = BaseAuthStrategy;
\ No newline at end of file
diff --git a/src/authStrategies/LegacySessionAuth.js b/src/authStrategies/LegacySessionAuth.js
index 34f59fe..084d431 100644
--- a/src/authStrategies/LegacySessionAuth.js
+++ b/src/authStrategies/LegacySessionAuth.js
@@ -9,7 +9,7 @@
'use strict';
-import BaseAuthStrategy from './BaseAuthStrategy.js'
+const BaseAuthStrategy = require('./BaseAuthStrategy');
/**
* Legacy session auth strategy
@@ -23,14 +23,14 @@ import BaseAuthStrategy from './BaseAuthStrategy.js'
* @param {string} options.session.WAToken2
*/
class LegacySessionAuth extends BaseAuthStrategy {
- constructor({ session, restartOnAuthFail } = {}) {
+ constructor({ session, restartOnAuthFail }={}) {
super();
this.session = session;
this.restartOnAuthFail = restartOnAuthFail;
}
async afterBrowserInitialized() {
- if (this.session) {
+ if(this.session) {
await this.client.mPage.evaluateOnNewDocument(session => {
if (document.referrer === 'https://whatsapp.com/') {
localStorage.clear();
@@ -39,14 +39,14 @@ class LegacySessionAuth extends BaseAuthStrategy {
localStorage.setItem('WAToken1', session.WAToken1);
localStorage.setItem('WAToken2', session.WAToken2);
}
-
+
localStorage.setItem('remember-me', 'true');
}, this.session);
}
}
async onAuthenticationNeeded() {
- if (this.session) {
+ if(this.session) {
this.session = null;
return {
failed: true,
@@ -63,7 +63,7 @@ class LegacySessionAuth extends BaseAuthStrategy {
return window.Store.MDBackend;
});
- if (isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.');
+ if(isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.');
const localStorage = JSON.parse(await this.client.mPage.evaluate(() => {
return JSON.stringify(window.localStorage);
@@ -78,4 +78,4 @@ class LegacySessionAuth extends BaseAuthStrategy {
}
}
-export default LegacySessionAuth;
\ No newline at end of file
+module.exports = LegacySessionAuth;
\ No newline at end of file
diff --git a/src/authStrategies/LocalAuth.js b/src/authStrategies/LocalAuth.js
index 179c644..5e4979d 100644
--- a/src/authStrategies/LocalAuth.js
+++ b/src/authStrategies/LocalAuth.js
@@ -9,9 +9,9 @@
'use strict';
-import path from 'path';
-import fs from 'fs';
-import BaseAuthStrategy from './BaseAuthStrategy.js';
+const path = require('path');
+const fs = require('fs');
+const BaseAuthStrategy = require('./BaseAuthStrategy');
/**
* Local directory-based authentication
@@ -20,15 +20,15 @@ import BaseAuthStrategy from './BaseAuthStrategy.js';
* @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/"
*/
class LocalAuth extends BaseAuthStrategy {
- constructor({ clientId, dataPath } = {}) {
+ constructor({ clientId, dataPath }={}) {
super();
const idRegex = /^[-_\w]+$/i;
- if (clientId && !idRegex.test(clientId)) {
+ if(clientId && !idRegex.test(clientId)) {
throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.');
}
- this.dataPath = path.resolve(dataPath || './.mywajs_auth/');
+ this.dataPath = path.resolve(dataPath || './.mywa_auth/');
this.clientId = clientId;
}
@@ -37,12 +37,12 @@ class LocalAuth extends BaseAuthStrategy {
const sessionDirName = this.clientId ? `session-${this.clientId}` : 'session';
const dirPath = path.join(this.dataPath, sessionDirName);
- if (playwrightOpts.userDataDir && playwrightOpts.userDataDir !== dirPath) {
+ if(playwrightOpts.userDataDir && playwrightOpts.userDataDir !== dirPath) {
throw new Error('LocalAuth is not compatible with a user-supplied userDataDir.');
}
fs.mkdirSync(dirPath, { recursive: true });
-
+
this.client.options.playwright = {
...playwrightOpts,
userDataDir: dirPath
@@ -53,12 +53,10 @@ class LocalAuth extends BaseAuthStrategy {
async logout() {
if (this.userDataDir) {
- try {
- return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true });
- } catch { }
+ return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true, force: true });
}
}
}
-export default LocalAuth;
\ No newline at end of file
+module.exports = LocalAuth;
\ No newline at end of file
diff --git a/src/authStrategies/NoAuth.js b/src/authStrategies/NoAuth.js
index 1c9f8ab..635c863 100644
--- a/src/authStrategies/NoAuth.js
+++ b/src/authStrategies/NoAuth.js
@@ -9,11 +9,13 @@
'use strict';
-import BaseAuthStrategy from './BaseAuthStrategy.js';
+const BaseAuthStrategy = require('./BaseAuthStrategy');
+
/**
* No session restoring functionality
* Will need to authenticate via QR code every time
*/
class NoAuth extends BaseAuthStrategy { }
-export default NoAuth;
\ No newline at end of file
+
+module.exports = NoAuth;
\ No newline at end of file
diff --git a/src/authStrategies/RemoteAuth.js b/src/authStrategies/RemoteAuth.js
index d32df63..9ea60e2 100644
--- a/src/authStrategies/RemoteAuth.js
+++ b/src/authStrategies/RemoteAuth.js
@@ -11,18 +11,18 @@
/* Require Optional Dependencies */
try {
- var fs = (await import('fs-extra'));
- var unzipper = (await import('unzipper'));
- var archiver = (await import('archiver'));
+ var fs = require('fs-extra');
+ var unzipper = require('unzipper');
+ var archiver = require('archiver');
} catch {
fs = undefined;
unzipper = undefined;
archiver = undefined;
}
-import path from 'path';
-import { Events } from '../util/Constants.js';
-import BaseAuthStrategy from './BaseAuthStrategy.js';
+const path = require('path');
+const { Events } = require('./../util/Constants');
+const BaseAuthStrategy = require('./BaseAuthStrategy');
/**
* Remote-based authentication
@@ -49,8 +49,8 @@ class RemoteAuth extends BaseAuthStrategy {
this.store = store;
this.clientId = clientId;
this.backupSyncIntervalMs = backupSyncIntervalMs;
- this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
- this.tempDir = `${this.dataPath}/wwebjs_temp_session`;
+ this.dataPath = path.resolve(dataPath || './.mywa_auth/');
+ this.tempDir = `${this.dataPath}/mywa_temp_session_${this.clientId}`;
this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files & Dirs in WWebJS to restore session */
}
@@ -210,4 +210,4 @@ class RemoteAuth extends BaseAuthStrategy {
}
}
-export default RemoteAuth;
\ No newline at end of file
+module.exports = RemoteAuth;
\ No newline at end of file
diff --git a/src/factories/ChatFactory.js b/src/factories/ChatFactory.js
index c7894df..6df75a3 100644
--- a/src/factories/ChatFactory.js
+++ b/src/factories/ChatFactory.js
@@ -6,11 +6,10 @@
* wa: 085157489446
* ig: amirul.dev
*/
-
'use strict';
-import PrivateChat from '../structures/PrivateChat.js';
-import GroupChat from '../structures/GroupChat.js';
+const PrivateChat = require('../structures/PrivateChat');
+const GroupChat = require('../structures/GroupChat');
class ChatFactory {
static create(client, data) {
@@ -22,4 +21,4 @@ class ChatFactory {
}
}
-export default ChatFactory;
\ No newline at end of file
+module.exports = ChatFactory;
\ No newline at end of file
diff --git a/src/factories/ContactFactory.js b/src/factories/ContactFactory.js
index a7f0f26..c514bee 100644
--- a/src/factories/ContactFactory.js
+++ b/src/factories/ContactFactory.js
@@ -9,8 +9,8 @@
'use strict';
-import PrivateContact from '../structures/PrivateContact.js';
-import BusinessContact from '../structures/BusinessContact.js';
+const PrivateContact = require('../structures/PrivateContact');
+const BusinessContact = require('../structures/BusinessContact');
class ContactFactory {
static create(client, data) {
@@ -22,4 +22,4 @@ class ContactFactory {
}
}
-export default ContactFactory;
\ No newline at end of file
+module.exports = ContactFactory;
\ No newline at end of file
diff --git a/src/structures/Base.js b/src/structures/Base.js
index 3b1cc03..cca0949 100644
--- a/src/structures/Base.js
+++ b/src/structures/Base.js
@@ -28,4 +28,4 @@ class Base {
_patch(data) { return data; }
}
-export default Base
\ No newline at end of file
+module.exports = Base;
\ No newline at end of file
diff --git a/src/structures/BusinessContact.js b/src/structures/BusinessContact.js
index 5efb2bd..7baef6a 100644
--- a/src/structures/BusinessContact.js
+++ b/src/structures/BusinessContact.js
@@ -9,7 +9,7 @@
'use strict';
-import Contact from "./Contact.js";
+const Contact = require('./Contact');
/**
* Represents a Business Contact on WhatsApp
@@ -27,4 +27,4 @@ class BusinessContact extends Contact {
}
-export default BusinessContact;
\ No newline at end of file
+module.exports = BusinessContact;
\ No newline at end of file
diff --git a/src/structures/Buttons.js b/src/structures/Buttons.js
index 562ee47..fa626f8 100644
--- a/src/structures/Buttons.js
+++ b/src/structures/Buttons.js
@@ -9,24 +9,21 @@
'use strict';
-import MessageMedia from "./MessageMedia.js";
+const MessageMedia = require('./MessageMedia');
+const Util = require('../util/Util');
/**
* Button spec used in Buttons constructor
* @typedef {Object} ButtonSpec
- * @property {string} body - The text to show on the button.
* @property {string=} id - Custom ID to set on the button. A random one will be generated if one is not passed.
- * @property {string=} url - Custom URL to set on the button. Optional and will change the type of the button
- * @property {string=} number - Custom URL to set on the button. Optional and will change the type of the button
+ * @property {string} body - The text to show on the button.
*/
/**
* @typedef {Object} FormattedButtonSpec
- * @property {number} index
- * @property {{displayText: string, url: string}=} urlButton
- * @property {{displayText: string, phoneNumber: string}=} callButton
- * @property {{displayText: string, id: string}=} quickReplyButton
- * @property {{regularButtons: {text: string, id: string}}=} regularButtons
+ * @property {string} buttonId
+ * @property {number} type
+ * @property {Object} buttonText
*/
/**
@@ -38,9 +35,8 @@ class Buttons {
* @param {ButtonSpec[]} buttons - See {@link ButtonSpec}
* @param {string?} title
* @param {string?} footer
- * @param {boolean?} templateOverride
*/
- constructor(body, buttons, title, footer, templateOverride = true) {
+ constructor(body, buttons, title, footer) {
/**
* Message body
* @type {string|MessageMedia}
@@ -72,58 +68,24 @@ class Buttons {
*/
this.buttons = this._format(buttons);
if(!this.buttons.length){ throw '[BT01] No buttons';}
-
- /**
- * Override buttons with templates
- * @type {boolean}
- */
- this.useTemplateButtons = templateOverride;
+
}
/**
* Creates button array from simple array
* @param {ButtonSpec[]} buttons
* @returns {FormattedButtonSpec[]}
+ * @example
+ * Input: [{id:'customId',body:'button1'},{body:'button2'},{body:'button3'},{body:'button4'}]
+ * Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}]
*/
_format(buttons){
- // Limit the buttons (max 3 of regular and 3 of special buttons) 5 buttons total at the same time
- const templateButtons = buttons.filter(button => button.url || button.number);
- const regularButtons = buttons.filter(button => !button.url && !button.number);
- buttons = regularButtons.concat(templateButtons);
-
- return buttons.map((button, index) => {
- if (button.url && button.number && button.id) throw 'Only pick one of the following (url/number/id)';
- if (button.number) {
- console.log('[WARNING] THIS FEATURE (CALL BUTTONS) IS UNSTABLE AND IS NOT TESTED OR EXPECTED TO WORK ON ALL PLATFORMS. Help test this feature with us on https://github.com/wwebjs/buttons-test');
- return {
- index,
- callButton: {
- displayText: button.body,
- phoneNumber: button.number || ''
- }
- };
- } else if (button.url) {
- console.log('[WARNING] THIS FEATURE (URL BUTTONS) IS UNSTABLE AND IS NOT TESTED OR EXPECTED TO WORK ON ALL PLATFORMS. Help test this feature with us on https://github.com/wwebjs/buttons-test');
- return {
- index,
- urlButton: {
- displayText: button.body,
- url: button.url || ''
- }
- };
- } else {
- return {
- index,
- quickReplyButton: {
- displayText: button.body,
- id: button.id || `${index}`
- }
- };
- }
-
+ buttons = buttons.slice(0,3); // phone users can only see 3 buttons, so lets limit this
+ return buttons.map((btn) => {
+ return {'buttonId':btn.id ? String(btn.id) : Util.generateHash(6),'buttonText':{'displayText':btn.body},'type':1};
});
}
}
-export default Buttons;
+module.exports = Buttons;
\ No newline at end of file
diff --git a/src/structures/Call.js b/src/structures/Call.js
index 4e9ecb6..0c6e560 100644
--- a/src/structures/Call.js
+++ b/src/structures/Call.js
@@ -7,84 +7,82 @@
* ig: amirul.dev
*/
-'use strict';
+"use strict";
-import Base from './Base.js';
+const Base = require("./Base");
/**
* Represents a Call on WhatsApp
* @extends {Base}
*/
class Call extends Base {
- constructor(client, data) {
- super(client);
+ constructor(client, data) {
+ super(client);
- if (data) this._patch(data);
- }
-
- _patch(data) {
- /**
- * Call ID
- * @type {string}
- */
- this.id = data.id;
- /**
- * From
- * @type {string}
- */
- this.from = data.peerJid;
- /**
- * Unix timestamp for when the call was created
- * @type {number}
- */
- this.timestamp = data.offerTime;
- /**
- * Is video
- * @type {boolean}
- */
- this.isVideo = data.isVideo;
- /**
- * Is Group
- * @type {boolean}
- */
- this.isGroup = data.isGroup;
- /**
- * Indicates if the call was sent by the current user
- * @type {boolean}
- */
- this.fromMe = data.outgoing;
- /**
- * Indicates if the call can be handled in waweb
- * @type {boolean}
- */
- this.canHandleLocally = data.canHandleLocally;
- /**
- * Indicates if the call Should be handled in waweb
- * @type {boolean}
- */
- this.webClientShouldHandle = data.webClientShouldHandle;
- /**
- *
- * @type {string}
- */
- this.state = data._state;
- /**
- * Object with participants
- * @type {object}
- */
- this.participants = data.participants;
-
- return super._patch(data);
- }
+ if (data) this._patch(data);
+ }
+ _patch(data) {
+ /**
+ * Call ID
+ * @type {string}
+ */
+ this.id = data.id;
+ /**
+ * From
+ * @type {string}
+ */
+ this.from = data.peerJid;
+ /**
+ * Unix timestamp for when the call was created
+ * @type {number}
+ */
+ this.timestamp = data.offerTime;
+ /**
+ * Is video
+ * @type {boolean}
+ */
+ this.isVideo = data.isVideo;
+ /**
+ * Is Group
+ * @type {boolean}
+ */
+ this.isGroup = data.isGroup;
/**
- * Reject the call
- */
- async reject() {
- return this.client.mPage.evaluate(({ peerJid, id }) => {
- return window.WWebJS.rejectCall(peerJid, id);
- }, { peerJid: this.from, id: this.id });
- }
+ * Indicates if the call was sent by the current user
+ * @type {boolean}
+ */
+ this.fromMe = data.outgoing;
+ /**
+ * Indicates if the call can be handled in waweb
+ * @type {boolean}
+ */
+ this.canHandleLocally = data.canHandleLocally;
+ /**
+ * Indicates if the call Should be handled in waweb
+ * @type {boolean}
+ */
+ this.webClientShouldHandle = data.webClientShouldHandle;
+ /**
+ * Object with participants
+ * @type {object}
+ */
+ this.participants = data.participants;
+
+ return super._patch(data);
+ }
+
+ /**
+ * Reject the call
+ */
+ async reject() {
+ return this.client.mPage.evaluate(
+ ({ peerJid, id }) => {
+ return window.WWebJS.rejectCall(peerJid, id);
+ },
+ { peerJid: this.from, id: this.id }
+ );
+ }
}
-export default Call;
\ No newline at end of file
+module.exports = Call;
diff --git a/src/structures/Chat.js b/src/structures/Chat.js
index 3bc2694..9b73970 100644
--- a/src/structures/Chat.js
+++ b/src/structures/Chat.js
@@ -7,295 +7,288 @@
* ig: amirul.dev
*/
-'use strict';
+"use strict";
-import Base from './Base.js';
-import Message from './Message.js';
+const Base = require("./Base");
+const Message = require("./Message");
/**
* Represents a Chat on WhatsApp
* @extends {Base}
*/
class Chat extends Base {
- constructor(client, data) {
- super(client);
-
- if (data) this._patch(data);
- }
-
- _patch(data) {
- /**
- * ID that represents the chat
- * @type {object}
- */
- this.id = data.id;
-
- /**
- * Title of the chat
- * @type {string}
- */
- this.name = data.formattedTitle;
-
- /**
- * Indicates if the Chat is a Group Chat
- * @type {boolean}
- */
- this.isGroup = data.isGroup;
-
- /**
- * Indicates if the Chat is readonly
- * @type {boolean}
- */
- this.isReadOnly = data.isReadOnly;
-
- /**
- * Amount of messages unread
- * @type {number}
- */
- this.unreadCount = data.unreadCount;
-
- /**
- * Unix timestamp for when the last activity occurred
- * @type {number}
- */
- this.timestamp = data.t;
-
- /**
- * Indicates if the Chat is archived
- * @type {boolean}
- */
- this.archived = data.archive;
-
- /**
- * Indicates if the Chat is pinned
- * @type {boolean}
- */
- this.pinned = !!data.pin;
-
- /**
- * Indicates if the chat is muted or not
- * @type {boolean}
- */
- this.isMuted = data.isMuted;
-
- /**
- * Unix timestamp for when the mute expires
- * @type {number}
- */
- this.muteExpiration = data.muteExpiration;
-
- return super._patch(data);
- }
+ constructor(client, data) {
+ super(client);
- /**
- * Send a message to this chat
- * @param {string|MessageMedia|Location} content
- * @param {MessageSendOptions} [options]
- * @returns {Promise} Message that was just sent
- */
- async sendMessage(content, options) {
- return this.client.sendMessage(this.id._serialized, content, options);
- }
+ if (data) this._patch(data);
+ }
+ _patch(data) {
/**
- * Set the message as seen
- * @returns {Promise} result
+ * ID that represents the chat
+ * @type {object}
*/
- async sendSeen() {
- return this.client.sendSeen(this.id._serialized);
- }
+ this.id = data.id;
/**
- * Clears all messages from the chat
- * @returns {Promise} result
+ * Title of the chat
+ * @type {string}
*/
- async clearMessages() {
- return this.client.mPage.evaluate(chatId => {
- return window.WWebJS.sendClearChat(chatId);
- }, this.id._serialized);
- }
+ this.name = data.formattedTitle;
/**
- * Deletes the chat
- * @returns {Promise} result
+ * Indicates if the Chat is a Group Chat
+ * @type {boolean}
*/
- async delete() {
- return this.client.mPage.evaluate(chatId => {
- return window.WWebJS.sendDeleteChat(chatId);
- }, this.id._serialized);
- }
+ this.isGroup = data.isGroup;
/**
- * Archives this chat
+ * Indicates if the Chat is readonly
+ * @type {boolean}
*/
- async archive() {
- return this.client.archiveChat(this.id._serialized);
- }
+ this.isReadOnly = data.isReadOnly;
/**
- * un-archives this chat
+ * Amount of messages unread
+ * @type {number}
*/
- async unarchive() {
- return this.client.unarchiveChat(this.id._serialized);
- }
+ this.unreadCount = data.unreadCount;
/**
- * Pins this chat
- * @returns {Promise} New pin state. Could be false if the max number of pinned chats was reached.
+ * Unix timestamp for when the last activity occurred
+ * @type {number}
*/
- async pin() {
- return this.client.pinChat(this.id._serialized);
- }
+ this.timestamp = data.t;
/**
- * Unpins this chat
- * @returns {Promise} New pin state
+ * Indicates if the Chat is archived
+ * @type {boolean}
*/
- async unpin() {
- return this.client.unpinChat(this.id._serialized);
- }
+ this.archived = data.archive;
/**
- * Mutes this chat forever, unless a date is specified
- * @param {?Date} unmuteDate Date at which the Chat will be unmuted, leave as is to mute forever
+ * Indicates if the Chat is pinned
+ * @type {boolean}
*/
- async mute(unmuteDate) {
- return this.client.muteChat(this.id._serialized, unmuteDate);
- }
+ this.pinned = !!data.pin;
/**
- * Unmutes this chat
+ * Indicates if the chat is muted or not
+ * @type {boolean}
*/
- async unmute() {
- return this.client.unmuteChat(this.id._serialized);
- }
+ this.isMuted = data.isMuted;
/**
- * temporary this chat forever, unless a date is specified
- * @param {?Date} ephemeralDuration Date at which the Chat will be temporary
+ * Unix timestamp for when the mute expires
+ * @type {number}
*/
- async ephemeral(ephemeralDuration) {
- return this.client.setEphemeral(this.id._serialized, ephemeralDuration)
- }
+ this.muteExpiration = data.muteExpiration;
/**
- * Unmutes this chat
- */
- async unmute() {
- return this.client.muteChat(this.id._serialized, false);
- }
-
- /**
- * Mark this chat as unread
- */
- async markUnread(){
- return this.client.markChatUnread(this.id._serialized);
- }
-
- /**
- * Loads chat messages, sorted from earliest to latest.
- * @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported.
- * @param {Number} [searchOptions.limit] The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages.
- * @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
- * @returns {Promise>}
- */
- async fetchMessages(searchOptions) {
- let messages = await this.client.mPage.evaluate(async ({ chatId, searchOptions }) => {
- const msgFilter = (m) => {
- if (m.isNotification) {
- return false; // dont include notification messages
- }
- if (searchOptions && searchOptions.fromMe && m.id.fromMe !== searchOptions.fromMe) {
- return false;
- }
- return true;
- };
-
- const chat = window.Store.Chat.get(chatId);
- let msgs = chat.msgs.getModelsArray().filter(msgFilter);
-
- if (searchOptions && searchOptions.limit > 0) {
- while (msgs.length < searchOptions.limit) {
- const loadedMessages = await window.Store.ConversationMsgs.loadEarlierMsgs(chat);
- if (!loadedMessages || !loadedMessages.length) break;
- msgs = [...loadedMessages.filter(msgFilter), ...msgs];
- }
-
- if (msgs.length > searchOptions.limit) {
- msgs.sort((a, b) => (a.t > b.t) ? 1 : -1);
- msgs = msgs.splice(msgs.length - searchOptions.limit);
- }
- }
-
- return msgs.map(m => window.WWebJS.getMessageModel(m));
-
- }, { chatId: this.id._serialized, searchOptions });
-
- return messages.map(m => new Message(this.client, m));
- }
-
- /**
- * Simulate typing in chat. This will last for 25 seconds.
- */
- async sendStateTyping() {
- return this.client.mPage.evaluate(chatId => {
- window.WWebJS.sendChatstate('typing', chatId);
- return true;
- }, this.id._serialized);
- }
-
- /**
- * Simulate recording audio in chat. This will last for 25 seconds.
- */
- async sendStateRecording() {
- return this.client.mPage.evaluate(chatId => {
- window.WWebJS.sendChatstate('recording', chatId);
- return true;
- }, this.id._serialized);
- }
-
- /**
- * Stops typing or recording in chat immediately.
- */
- async clearState() {
- return this.client.mPage.evaluate(chatId => {
- window.WWebJS.sendChatstate('stop', chatId);
- return true;
- }, this.id._serialized);
- }
-
- /**
- * Returns the Contact that corresponds to this Chat.
- * @returns {Promise}
- */
- async getContact() {
- return await this.client.getContactById(this.id._serialized);
- }
-
- /**
- * Returns array of all Labels assigned to this Chat
- * @returns {Promise>}
- */
- async getLabels() {
- return this.client.getChatLabels(this.id._serialized);
- }
-
- /**
- *
- * @param {String} type
- * @returns {Promise}
+ * Last message fo chat
+ * @type {Message}
*/
- async reportBlockClear(type = 'AccountInfoReport') {
- await this.client.mPage.evaluate(async ({ chatId, type }) => {
- const Wid = window.Store.WidFactory.createWid(chatId)
- const chat = window.Store.Chat.get(Wid)
-
- const SpamFlow = window.Store.SpamFlow
- if (!(type in SpamFlow)) throw `Type Not Found\n\n${Object.keys(SpamFlow).join('\n')}`
-
- return await window.Store.GroupUtils.sendSpamBlockClear(chat, SpamFlow[type])
- }, { chatId: this.id._serialized, type })
- }
+ this.lastMessage = data.lastMessage
+ ? new Message(super.client, data.lastMessage)
+ : undefined;
+
+ return super._patch(data);
+ }
+
+ /**
+ * Send a message to this chat
+ * @param {string|MessageMedia|Location} content
+ * @param {MessageSendOptions} [options]
+ * @returns {Promise} Message that was just sent
+ */
+ async sendMessage(content, options) {
+ return this.client.sendMessage(this.id._serialized, content, options);
+ }
+
+ /**
+ * Set the message as seen
+ * @returns {Promise} result
+ */
+ async sendSeen() {
+ return this.client.sendSeen(this.id._serialized);
+ }
+
+ /**
+ * Clears all messages from the chat
+ * @returns {Promise} result
+ */
+ async clearMessages() {
+ return this.client.mPage.evaluate((chatId) => {
+ return window.WWebJS.sendClearChat(chatId);
+ }, this.id._serialized);
+ }
+
+ /**
+ * Deletes the chat
+ * @returns {Promise} result
+ */
+ async delete() {
+ return this.client.mPage.evaluate((chatId) => {
+ return window.WWebJS.sendDeleteChat(chatId);
+ }, this.id._serialized);
+ }
+
+ /**
+ * Archives this chat
+ */
+ async archive() {
+ return this.client.archiveChat(this.id._serialized);
+ }
+
+ /**
+ * un-archives this chat
+ */
+ async unarchive() {
+ return this.client.unarchiveChat(this.id._serialized);
+ }
+
+ /**
+ * Pins this chat
+ * @returns {Promise} New pin state. Could be false if the max number of pinned chats was reached.
+ */
+ async pin() {
+ return this.client.pinChat(this.id._serialized);
+ }
+
+ /**
+ * Unpins this chat
+ * @returns {Promise} New pin state
+ */
+ async unpin() {
+ return this.client.unpinChat(this.id._serialized);
+ }
+
+ /**
+ * Mutes this chat forever, unless a date is specified
+ * @param {?Date} unmuteDate Date at which the Chat will be unmuted, leave as is to mute forever
+ */
+ async mute(unmuteDate) {
+ return this.client.muteChat(this.id._serialized, unmuteDate);
+ }
+
+ /**
+ * Unmutes this chat
+ */
+ async unmute() {
+ return this.client.unmuteChat(this.id._serialized);
+ }
+
+ /**
+ * Mark this chat as unread
+ */
+ async markUnread() {
+ return this.client.markChatUnread(this.id._serialized);
+ }
+
+ /**
+ * Loads chat messages, sorted from earliest to latest.
+ * @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported.
+ * @param {Number} [searchOptions.limit] The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages.
+ * @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
+ * @returns {Promise>}
+ */
+ async fetchMessages(searchOptions) {
+ let messages = await this.client.mPage.evaluate(
+ async (chatId, searchOptions) => {
+ const msgFilter = (m) => {
+ if (m.isNotification) {
+ return false; // dont include notification messages
+ }
+ if (
+ searchOptions &&
+ searchOptions.fromMe !== undefined &&
+ m.id.fromMe !== searchOptions.fromMe
+ ) {
+ return false;
+ }
+ return true;
+ };
+
+ const chat = window.Store.Chat.get(chatId);
+ let msgs = chat.msgs.getModelsArray().filter(msgFilter);
+
+ if (searchOptions && searchOptions.limit > 0) {
+ while (msgs.length < searchOptions.limit) {
+ const loadedMessages =
+ await window.Store.ConversationMsgs.loadEarlierMsgs(chat);
+ if (!loadedMessages || !loadedMessages.length) break;
+ msgs = [...loadedMessages.filter(msgFilter), ...msgs];
+ }
+
+ if (msgs.length > searchOptions.limit) {
+ msgs.sort((a, b) => (a.t > b.t ? 1 : -1));
+ msgs = msgs.splice(msgs.length - searchOptions.limit);
+ }
+ }
+
+ return msgs.map((m) => window.WWebJS.getMessageModel(m));
+ },
+ this.id._serialized,
+ searchOptions
+ );
+
+ return messages.map((m) => new Message(this.client, m));
+ }
+
+ /**
+ * Simulate typing in chat. This will last for 25 seconds.
+ */
+ async sendStateTyping() {
+ return this.client.mPage.evaluate((chatId) => {
+ window.WWebJS.sendChatstate("typing", chatId);
+ return true;
+ }, this.id._serialized);
+ }
+
+ /**
+ * Simulate recording audio in chat. This will last for 25 seconds.
+ */
+ async sendStateRecording() {
+ return this.client.mPage.evaluate((chatId) => {
+ window.WWebJS.sendChatstate("recording", chatId);
+ return true;
+ }, this.id._serialized);
+ }
+
+ /**
+ * Stops typing or recording in chat immediately.
+ */
+ async clearState() {
+ return this.client.mPage.evaluate((chatId) => {
+ window.WWebJS.sendChatstate("stop", chatId);
+ return true;
+ }, this.id._serialized);
+ }
+
+ /**
+ * Returns the Contact that corresponds to this Chat.
+ * @returns {Promise}
+ */
+ async getContact() {
+ return await this.client.getContactById(this.id._serialized);
+ }
+
+ /**
+ * Returns array of all Labels assigned to this Chat
+ * @returns {Promise>}
+ */
+ async getLabels() {
+ return this.client.getChatLabels(this.id._serialized);
+ }
+
+ /**
+ * Add or remove labels to this Chat
+ * @param {Array} labelIds
+ * @returns {Promise}
+ */
+ async changeLabels(labelIds) {
+ return this.client.addOrRemoveLabels(labelIds, [this.id._serialized]);
+ }
}
-export default Chat;
+module.exports = Chat;
diff --git a/src/structures/ClientInfo.js b/src/structures/ClientInfo.js
index 4a3f766..891fdf8 100644
--- a/src/structures/ClientInfo.js
+++ b/src/structures/ClientInfo.js
@@ -9,7 +9,7 @@
'use strict';
-import Base from './Base.js';
+const Base = require('./Base');
/**
* Current connection information
@@ -77,4 +77,4 @@ class ClientInfo extends Base {
}
}
-export default ClientInfo;
\ No newline at end of file
+module.exports = ClientInfo;
\ No newline at end of file
diff --git a/src/structures/Contact.js b/src/structures/Contact.js
index 19d8328..3ca42dd 100644
--- a/src/structures/Contact.js
+++ b/src/structures/Contact.js
@@ -9,7 +9,7 @@
'use strict';
-import Base from './Base.js';
+const Base = require('./Base');
/**
* ID that represents a contact
@@ -165,9 +165,10 @@ class Contact extends Base {
await this.client.mPage.evaluate(async (contactId) => {
const contact = window.Store.Contact.get(contactId);
- await window.Store.BlockContact.blockContact(contact);
+ await window.Store.BlockContact.blockContact({contact});
}, this.id._serialized);
+ this.isBlocked = true;
return true;
}
@@ -183,6 +184,7 @@ class Contact extends Base {
await window.Store.BlockContact.unblockContact(contact);
}, this.id._serialized);
+ this.isBlocked = false;
return true;
}
@@ -212,4 +214,4 @@ class Contact extends Base {
}
-export default Contact;
+module.exports = Contact;
\ No newline at end of file
diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js
index e9f8b24..9eac8d4 100644
--- a/src/structures/GroupChat.js
+++ b/src/structures/GroupChat.js
@@ -9,7 +9,7 @@
'use strict';
-import Chat from './Chat.js';
+const Chat = require('./Chat');
/**
* Group participant information
@@ -24,434 +24,414 @@ import Chat from './Chat.js';
* @extends {Chat}
*/
class GroupChat extends Chat {
-_patch(data) {
-this.groupMetadata = data.groupMetadata;
-
-return super._patch(data);
-}
-
-/**
- * Gets the group owner
- * @type {ContactId}
- */
-get owner() {
-return this.groupMetadata.owner;
-}
-
-/**
- * Gets the date at which the group was created
- * @type {date}
- */
-get createdAt() {
-return new Date(this.groupMetadata.creation * 1000);
-}
-
-/**
- * Gets the group description
- * @type {string}
- */
-get description() {
-return this.groupMetadata.desc;
-}
-
-/**
- * Gets the group participants
- * @type {Array}
- */
-get participants() {
-return this.groupMetadata.participants;
-}
-
-/**
- * Adds a list of participants by ID to the group
- * @param {Array} participantIds
- * @returns {Promise}
- */
-async addParticipants(participantIds, options = {}) {
-return await this.client.mPage.evaluate(async ({groupId, participantIds, options}) => {
-const { sleep = [250, 500], autoSendInviteV4 = true, comment = '' } = options;
-const groupWid = window.Store.WidFactory.createWid(groupId);
-const group = await window.Store.Chat.find(groupWid);
-!Array.isArray(participantIds) && (participantIds = [participantIds]);
-
-let participantsToAdd = await Promise.all(participantIds.map(async p => {
-const wid = window.Store.WidFactory.createWid(p);
-return await window.Store.Contact.find(wid);
-}));
-
-const participantData = {};
-
-const addParticipantResultCodes = {
-default: 'An unknown error occupied while adding a participant',
-isGroupEmpty: 'AddParticipantsError: You can\'t add a participant to an empty group',
-iAmNotAdmin: 'AddParticipantsError: You have no admin rights to add a participant to a group',
-200: 'The participant was added successfully',
-403: 'The participant can be added by sending private invitation only',
-408: 'You cannot add this participant because they recently left the group',
-409: 'The participant is already a group member',
-417: 'The participant can\'t be added to the community. You can invite them privately to join this group through its invite link',
-419: 'The participant can\'t be added because the group is full'
-};
-
-await window.Store.GroupMetadata.queryAndUpdate(groupWid);
-const groupMetadata = group.groupMetadata;
-const groupParticipants = groupMetadata?.participants;
-
-if (!groupParticipants) {
-return addParticipantResultCodes.isGroupEmpty;
-}
-
-if (!groupParticipants.canAdd()) {
-return addParticipantResultCodes.iAmNotAdmin;
-}
-
-const _getSleepTime = (sleep) => {
-if (!Array.isArray(sleep) || sleep.length === 2 && sleep[0] === sleep[1]) {
-return sleep;
-}
-if (sleep.length === 1) {
-return sleep[0];
-}
-(sleep[1] - sleep[0]) < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100);
-return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0];
-};
-
-for (const participant of participantsToAdd) {
-const participantId = participant.id._serialized;
-
-participantData[participantId] = {
-code: undefined,
-message: undefined,
-isInviteV4Sent: false
-};
-
-if (groupParticipants.some(p => p.id._serialized === participantId)) {
-participantData[participantId].code = 409;
-participantData[participantId].message = addParticipantResultCodes[409];
-continue;
-}
-
-const rpcResult =
-await window.WWebJS.getAddParticipantsRpcResult(groupMetadata, groupWid, participant.id);
-const { code: rpcResultCode } = rpcResult;
-
-if (rpcResultCode === 403) {
-window.Store.ContactCollection.gadd(participant.id, { silent: true });
-}
-
-participantData[participantId].code = rpcResultCode;
-participantData[participantId].message = rpcResultCode === -1
-? rpcResult.message
-: addParticipantResultCodes[rpcResultCode] || addParticipantResultCodes.default;
-
-if (autoSendInviteV4 && rpcResultCode === 403) {
-let userChat, isInviteV4Sent = false;
-
-if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' &&
-(userChat = await window.Store.Chat.find(participant.id))) {
-const groupName = group.formattedTitle || group.name;
-const res = await window.Store.GroupInviteV4.sendGroupInviteMessage(
-userChat,
-group.id._serialized,
-groupName,
-rpcResult.inviteV4Code,
-rpcResult.inviteV4CodeExp,
-comment,
-await window.WWebJS.getProfilePicThumbToBase64(groupWid)
-);
-isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6')
-? res === 'OK'
-: res.messageSendResult === 'OK';
-}
-
-participantData[participantId].isInviteV4Sent = isInviteV4Sent;
-}
-
-sleep &&
-participantsToAdd.length > 1 &&
-participantsToAdd.indexOf(participant) !== participantsToAdd.length - 1 &&
-(await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep))));
-}
-
-return participantData;
-}, {groupId: this.id._serialized, participantIds, options});
-}
-/**
- * Removes a list of participants by ID to the group
- * @param {Array} participantIds
- * @returns {Promise}
- */
-async removeParticipants(participantIds) {
-if (!Array.isArray(participantIds)) {
-participantIds = [participantIds]
-} else {
-participantIds = participantIds
-}
-
-return await this.client.mPage.evaluate(async ({ chatId, participantIds }) => {
-return await window.WWebJS.group.removeParticipants(chatId, participantIds)
-}, { chatId: this.id._serialized, participantIds });
-}
-
-/**
- * Promotes participants by IDs to admins
- * @param {Array} participantIds
- * @returns {Promise<{ status: number }>} Object with status code indicating if the operation was successful
- */
-async promoteParticipants(participantIds) {
-if (!Array.isArray(participantIds)) {
-participantIds = [participantIds]
-} else {
-participantIds = participantIds
-}
-
-return await this.client.mPage.evaluate(async ({ chatId, participantIds }) => {
-return await window.WWebJS.group.promoteParticipants(chatId, participantIds)
-}, { chatId: this.id._serialized, participantIds });
-}
-
-/**
- * Demotes participants by IDs to regular users
- * @param {Array} participantIds
- * @returns {Promise<{ status: number }>} Object with status code indicating if the operation was successful
- */
-async demoteParticipants(participantIds) {
-if (!Array.isArray(participantIds)) {
-participantIds = [participantIds]
-} else {
-participantIds = participantIds
-}
-
-return await this.client.mPage.evaluate(async ({ chatId, participantIds }) => {
-return await window.WWebJS.group.demoteParticipants(chatId, participantIds)
-}, { chatId: this.id._serialized, participantIds });
-}
-
-/**
- * Updates the group subject
- * @param {string} subject
- * @returns {Promise} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.
- */
-async setSubject(subject) {
-const success = await this.client.mPage.evaluate(async ({ chatId, subject }) => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-try {
-await window.Store.GroupUtils.setGroupSubject(chatWid, subject);
-return true;
-} catch (err) {
-if (err.name === 'ServerStatusCodeError') return false;
-throw err;
-}
-}, { chatId: this.id._serialized, subject });
-
-if (!success) return false;
-this.name = subject;
-return true;
-}
-
-/**
- * Updates the group description
- * @param {string} description
- * @returns {Promise} Returns true if the description was properly updated. This can return false if the user does not have the necessary permissions.
- */
-async setDescription(description) {
-const success = await this.client.mPage.evaluate(async ({ chatId, description }) => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-let descId = window.Store.GroupMetadata.get(chatWid).descId;
-try {
-await window.Store.GroupUtils.setGroupDescription(chatWid, description, window.Store.MsgKey.newId(), descId);
-return true;
-} catch (err) {
-if (err.name === 'ServerStatusCodeError') return false;
-throw err;
-}
-}, { chatId: this.id._serialized, description });
-
-if (!success) return false;
-this.groupMetadata.desc = description;
-return true;
-}
-
-/**
- * Updates the group settings to only allow admins to send messages.
- * @param {boolean} [adminsOnly=true] Enable or disable this option
- * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
- */
-async setMessagesAdminsOnly(adminsOnly = true) {
-const success = await this.client.mPage.evaluate(async ({ chatId, adminsOnly }) => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-try {
-await window.Store.GroupUtils.setGroupProperty(chatWid, 'announcement', adminsOnly ? 1 : 0);
-return true;
-} catch (err) {
-if (err.name === 'ServerStatusCodeError') return false;
-throw err;
-}
-}, { chatId: this.id._serialized, adminsOnly });
-
-if (!success) return false;
-
-this.groupMetadata.announce = adminsOnly;
-return true;
-}
-
-/**
- * set the group settings to enable or disable approval mode
- * @param {boolean} [adminsOnly=true] Enable or disable this option
- * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
- */
-async setMemberApprovalMode(adminsOnly = true) {
-const succes = await this.client.mPage.evaluate(async ({ chatId, adminsOnly }) => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-try {
-await window.Store.GroupUtils.setGroupProperty(chatWid, 'membership_approval_mode', adminsOnly ? 1 : 0);
-return true;
-} catch (err) {
-if (err.name === 'ServerStatusCodeError') return false;
-throw err;
-}
-}, { chatId: this.id._serialized, adminsOnly })
-
-if (!success) return false;
-
-this.groupMetadata.membershipApprovalMode = adminsOnly;
-return true;
-}
-
-/**
- * Updates the group settings to only allow admins to edit group info (title, description, photo).
- * @param {boolean} [adminsOnly=true] Enable or disable this option
- * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
- */
-async setInfoAdminsOnly(adminsOnly = true) {
-const success = await this.client.mPage.evaluate(async ({ chatId, adminsOnly }) => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-try {
-await window.Store.GroupUtils.setGroupProperty(chatWid, 'restrict', adminsOnly ? 1 : 0);
-return true;
-} catch (err) {
-if (err.name === 'ServerStatusCodeError') return false;
-throw err;
-}
-}, { chatId: this.id._serialized, adminsOnly });
-
-if (!success) return false;
-
-this.groupMetadata.restrict = adminsOnly;
-return true;
-}
-
-/**
- * Gets the invite code for a specific group
- * @returns {Promise} Group's invite code
- */
-async getInviteCode() {
-const codeRes = await this.client.mPage.evaluate(async chatId => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-return window.Store.Invite.queryGroupInviteCode(chatWid);
-}, this.id._serialized);
-
-return codeRes.code;
-}
-
-/**
- * Invalidates the current group invite code and generates a new one
- * @returns {Promise} New invite code
- */
-async revokeInvite() {
-const codeRes = await this.client.mPage.evaluate(chatId => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-return window.Store.Invite.resetGroupInviteCode(chatWid);
-}, this.id._serialized);
-
-return codeRes.code;
-}
-
-/**
- * Deletes the group's picture.
- * @returns {Promise} Returns true if the picture was properly deleted. This can return false if the user does not have the necessary permissions.
- */
-async deletePicture() {
-const success = await this.client.mPage.evaluate((chatid) => {
-return window.WWebJS.deletePicture(chatid);
-}, this.id._serialized);
-
-return success;
-}
-
-/**
- * Sets the group's picture.
- * @param {MessageMedia} media
- * @returns {Promise} Returns true if the picture was properly updated. This can return false if the user does not have the necessary permissions.
- */
-async setPicture(media, type = 'normal') {
-const success = await this.client.mPage.evaluate(({ chatid, media, type }) => {
-return window.WWebJS.setPicture(chatid, media, type);
-}, { chatId: this.id._serialized, media, type });
-
-return success;
-}
-
-/**
- * Makes the bot leave the group
- * @returns {Promise}
- */
-async leave() {
-await this.client.mPage.evaluate(async chatId => {
-const chatWid = window.Store.WidFactory.createWid(chatId);
-const chat = await window.Store.Chat.find(chatWid);
-return window.Store.GroupUtils.sendExitGroup(chat);
-}, this.id._serialized);
-}
-
-/**
- *
- * @param {String} type
- * @returns {Promise}
- */
-async reportExitClear(type = 'AccountInfoReport') {
-await this.client.mPage.evaluate(async ({ chatId, type }) => {
-const Wid = window.Store.WidFactory.createWid(chatId)
-const chat = window.Store.Chat.get(Wid)
-
-const SpamFlow = window.Store.SpamFlow
-if (!(type in SpamFlow)) throw `Type Not Found\n\n${Object.keys(SpamFlow).join('\n')}`
-
-return await window.Store.GroupUtils.sendSpamExitClear(chat, SpamFlow[type])
-}, { chatId: this.id._serialized, type })
-}
-
-/**
- *
- * @param {String} participant
- * @returns {Promise}
- */
-async rejectRequest(participant) {
-await this.client.mPage.evaluate(({ chatId, participant }) => {
-return window.WWebJS.group.reject(chatId, participant)
-}, { chatId: this.id._serialized, participant })
-}
-
-/**
- *
- * @param {String} participant
- * @returns {Promise}
- */
-async approveRequest(participant) {
-await this.client.mPage.evaluate(({ chatId, participant }) => {
-return window.WWebJS.group.approve(chatId, participant)
-}, { chatId: this.id._serialized, participant })
-}
-
-/**
- *
- * @returns {Promise}
- */
-async getMemberRequest() {
-return await this.client.mPage.evaluate((chatId) => {
-return window.WWebJS.group.getMembershipRequests(chatId)
-}, this.id._serialized)
-}
-}
-
-export default GroupChat
\ No newline at end of file
+ _patch(data) {
+ this.groupMetadata = data.groupMetadata;
+
+ return super._patch(data);
+ }
+
+ /**
+ * Gets the group owner
+ * @type {ContactId}
+ */
+ get owner() {
+ return this.groupMetadata.owner;
+ }
+
+ /**
+ * Gets the date at which the group was created
+ * @type {date}
+ */
+ get createdAt() {
+ return new Date(this.groupMetadata.creation * 1000);
+ }
+
+ /**
+ * Gets the group description
+ * @type {string}
+ */
+ get description() {
+ return this.groupMetadata.desc;
+ }
+
+ /**
+ * Gets the group participants
+ * @type {Array}
+ */
+ get participants() {
+ return this.groupMetadata.participants;
+ }
+
+ /**
+ * Adds a list of participants by ID to the group
+ * @param {Array} participantIds
+ * @returns {Promise}
+ */
+ async addParticipants(participantIds, options = {}) {
+ return await this.client.mPage.evaluate(async ({groupId, participantIds, options}) => {
+ const { sleep = [250, 500], autoSendInviteV4 = true, comment = '' } = options;
+ const participantData = {};
+
+ !Array.isArray(participantIds) && (participantIds = [participantIds]);
+ const groupWid = window.Store.WidFactory.createWid(groupId);
+ const group = await window.Store.Chat.find(groupWid);
+ const participantWids = participantIds.map((p) => window.Store.WidFactory.createWid(p));
+
+ const errorCodes = {
+ default: 'An unknown error occupied while adding a participant',
+ isGroupEmpty: 'AddParticipantsError: The participant can\'t be added to an empty group',
+ iAmNotAdmin: 'AddParticipantsError: You have no admin rights to add a participant to a group',
+ 200: 'The participant was added successfully',
+ 403: 'The participant can be added by sending private invitation only',
+ 404: 'The phone number is not registered on WhatsApp',
+ 408: 'You cannot add this participant because they recently left the group',
+ 409: 'The participant is already a group member',
+ 417: 'The participant can\'t be added to the community. You can invite them privately to join this group through its invite link',
+ 419: 'The participant can\'t be added because the group is full'
+ };
+
+ await window.Store.GroupMetadata.queryAndUpdate(groupWid);
+ const groupMetadata = group.groupMetadata;
+ const groupParticipants = groupMetadata?.participants;
+
+ if (!groupParticipants) {
+ return errorCodes.isGroupEmpty;
+ }
+
+ if (!group.iAmAdmin()) {
+ return errorCodes.iAmNotAdmin;
+ }
+
+ const _getSleepTime = (sleep) => {
+ if (!Array.isArray(sleep) || sleep.length === 2 && sleep[0] === sleep[1]) {
+ return sleep;
+ }
+ if (sleep.length === 1) {
+ return sleep[0];
+ }
+ (sleep[1] - sleep[0]) < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100);
+ return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0];
+ };
+
+ for (const pWid of participantWids) {
+ const pId = pWid._serialized;
+
+ participantData[pId] = {
+ code: undefined,
+ message: undefined,
+ isInviteV4Sent: false
+ };
+
+ if (groupParticipants.some(p => p.id._serialized === pId)) {
+ participantData[pId].code = 409;
+ participantData[pId].message = errorCodes[409];
+ continue;
+ }
+
+ if (!(await window.Store.QueryExist(pWid))?.wid) {
+ participantData[pId].code = 404;
+ participantData[pId].message = errorCodes[404];
+ continue;
+ }
+
+ const rpcResult =
+ await window.WWebJS.getAddParticipantsRpcResult(groupMetadata, groupWid, pWid);
+ const { code: rpcResultCode } = rpcResult;
+
+ participantData[pId].code = rpcResultCode;
+ participantData[pId].message =
+ errorCodes[rpcResultCode] || errorCodes.default;
+
+ if (autoSendInviteV4 && rpcResultCode === 403) {
+ let userChat, isInviteV4Sent = false;
+ window.Store.ContactCollection.gadd(pWid, { silent: true });
+
+ if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' &&
+ (userChat = await window.Store.Chat.find(pWid))) {
+ const groupName = group.formattedTitle || group.name;
+ const res = await window.Store.GroupInviteV4.sendGroupInviteMessage(
+ userChat,
+ group.id._serialized,
+ groupName,
+ rpcResult.inviteV4Code,
+ rpcResult.inviteV4CodeExp,
+ comment,
+ await window.WWebJS.getProfilePicThumbToBase64(groupWid)
+ );
+ isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6')
+ ? res === 'OK'
+ : res.messageSendResult === 'OK';
+ }
+
+ participantData[pId].isInviteV4Sent = isInviteV4Sent;
+ }
+
+ sleep &&
+ participantWids.length > 1 &&
+ participantWids.indexOf(pWid) !== participantWids.length - 1 &&
+ (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep))));
+ }
+
+ return participantData;
+ }, { groupId: this.id._serialized, participantIds, options });
+}
+ /**
+ * Removes a list of participants by ID to the group
+ * @param {Array} participantIds
+ * @returns {Promise}
+ */
+ async removeParticipants(participantIds) {
+ return await this.client.mPage.evaluate(async (chatId, participantIds) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ const chat = await window.Store.Chat.find(chatWid);
+ const participants = participantIds.map(p => {
+ return chat.groupMetadata.participants.get(p);
+ }).filter(p => Boolean(p));
+ await window.Store.GroupParticipants.removeParticipants(chat, participants);
+ return { status: 200 };
+ }, this.id._serialized, participantIds);
+ }
+
+ /**
+ * Promotes participants by IDs to admins
+ * @param {Array} participantIds
+ * @returns {Promise<{ status: number }>} Object with status code indicating if the operation was successful
+ */
+ async promoteParticipants(participantIds) {
+ return await this.client.mPage.evaluate(async (chatId, participantIds) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ const chat = await window.Store.Chat.find(chatWid);
+ const participants = participantIds.map(p => {
+ return chat.groupMetadata.participants.get(p);
+ }).filter(p => Boolean(p));
+ await window.Store.GroupParticipants.promoteParticipants(chat, participants);
+ return { status: 200 };
+ }, this.id._serialized, participantIds);
+ }
+
+ /**
+ * Demotes participants by IDs to regular users
+ * @param {Array} participantIds
+ * @returns {Promise<{ status: number }>} Object with status code indicating if the operation was successful
+ */
+ async demoteParticipants(participantIds) {
+ return await this.client.mPage.evaluate(async (chatId, participantIds) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ const chat = await window.Store.Chat.find(chatWid);
+ const participants = participantIds.map(p => {
+ return chat.groupMetadata.participants.get(p);
+ }).filter(p => Boolean(p));
+ await window.Store.GroupParticipants.demoteParticipants(chat, participants);
+ return { status: 200 };
+ }, this.id._serialized, participantIds);
+ }
+
+ /**
+ * Updates the group subject
+ * @param {string} subject
+ * @returns {Promise} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.
+ */
+ async setSubject(subject) {
+ const success = await this.client.mPage.evaluate(async (chatId, subject) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ try {
+ await window.Store.GroupUtils.setGroupSubject(chatWid, subject);
+ return true;
+ } catch (err) {
+ if(err.name === 'ServerStatusCodeError') return false;
+ throw err;
+ }
+ }, this.id._serialized, subject);
+
+ if(!success) return false;
+ this.name = subject;
+ return true;
+ }
+
+ /**
+ * Updates the group description
+ * @param {string} description
+ * @returns {Promise} Returns true if the description was properly updated. This can return false if the user does not have the necessary permissions.
+ */
+ async setDescription(description) {
+ const success = await this.client.mPage.evaluate(async (chatId, description) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ let descId = window.Store.GroupMetadata.get(chatWid).descId;
+ let newId = await window.Store.MsgKey.newId();
+ try {
+ await window.Store.GroupUtils.setGroupDescription(chatWid, description, newId, descId);
+ return true;
+ } catch (err) {
+ if(err.name === 'ServerStatusCodeError') return false;
+ throw err;
+ }
+ }, this.id._serialized, description);
+
+ if(!success) return false;
+ this.groupMetadata.desc = description;
+ return true;
+ }
+
+ /**
+ * Updates the group settings to only allow admins to send messages.
+ * @param {boolean} [adminsOnly=true] Enable or disable this option
+ * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
+ */
+ async setMessagesAdminsOnly(adminsOnly=true) {
+ const success = await this.client.mPage.evaluate(async (chatId, adminsOnly) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ try {
+ await window.Store.GroupUtils.setGroupProperty(chatWid, 'announcement', adminsOnly ? 1 : 0);
+ return true;
+ } catch (err) {
+ if(err.name === 'ServerStatusCodeError') return false;
+ throw err;
+ }
+ }, this.id._serialized, adminsOnly);
+
+ if(!success) return false;
+
+ this.groupMetadata.announce = adminsOnly;
+ return true;
+ }
+
+ /**
+ * Updates the group settings to only allow admins to edit group info (title, description, photo).
+ * @param {boolean} [adminsOnly=true] Enable or disable this option
+ * @returns {Promise} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions.
+ */
+ async setInfoAdminsOnly(adminsOnly=true) {
+ const success = await this.client.mPage.evaluate(async (chatId, adminsOnly) => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ try {
+ await window.Store.GroupUtils.setGroupProperty(chatWid, 'restrict', adminsOnly ? 1 : 0);
+ return true;
+ } catch (err) {
+ if(err.name === 'ServerStatusCodeError') return false;
+ throw err;
+ }
+ }, this.id._serialized, adminsOnly);
+
+ if(!success) return false;
+
+ this.groupMetadata.restrict = adminsOnly;
+ return true;
+ }
+
+ /**
+ * Deletes the group's picture.
+ * @returns {Promise} Returns true if the picture was properly deleted. This can return false if the user does not have the necessary permissions.
+ */
+ async deletePicture() {
+ const success = await this.client.mPage.evaluate((chatid) => {
+ return window.WWebJS.deletePicture(chatid);
+ }, this.id._serialized);
+
+ return success;
+ }
+
+ /**
+ * Sets the group's picture.
+ * @param {MessageMedia} media
+ * @returns {Promise} Returns true if the picture was properly updated. This can return false if the user does not have the necessary permissions.
+ */
+ async setPicture(media) {
+ const success = await this.client.mPage.evaluate((chatid, media) => {
+ return window.WWebJS.setPicture(chatid, media);
+ }, this.id._serialized, media);
+
+ return success;
+ }
+
+ /**
+ * Gets the invite code for a specific group
+ * @returns {Promise} Group's invite code
+ */
+ async getInviteCode() {
+ const codeRes = await this.client.mPage.evaluate(async chatId => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ return window.Store.Invite.queryGroupInviteCode(chatWid);
+ }, this.id._serialized);
+
+ return codeRes.code;
+ }
+
+ /**
+ * Invalidates the current group invite code and generates a new one
+ * @returns {Promise} New invite code
+ */
+ async revokeInvite() {
+ const codeRes = await this.client.mPage.evaluate(chatId => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ return window.Store.Invite.resetGroupInviteCode(chatWid);
+ }, this.id._serialized);
+
+ return codeRes.code;
+ }
+
+ /**
+ * An object that handles the information about the group membership request
+ * @typedef {Object} GroupMembershipRequest
+ * @property {Object} id The wid of a user who requests to enter the group
+ * @property {Object} addedBy The wid of a user who created that request
+ * @property {Object|null} parentGroupId The wid of a community parent group to which the current group is linked
+ * @property {string} requestMethod The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin
+ * @property {number} t The timestamp the request was created at
+ */
+
+ /**
+ * Gets an array of membership requests
+ * @returns {Promise>} An array of membership requests
+ */
+ async getGroupMembershipRequests() {
+ return await this.client.getGroupMembershipRequests(this.id._serialized);
+ }
+
+ /**
+ * An object that handles the result for membership request action
+ * @typedef {Object} MembershipRequestActionResult
+ * @property {string} requesterId User ID whos membership request was approved/rejected
+ * @property {number} error An error code that occurred during the operation for the participant
+ * @property {string} message A message with a result of membership request action
+ */
+
+ /**
+ * An object that handles options for {@link approveGroupMembershipRequests} and {@link rejectGroupMembershipRequests} methods
+ * @typedef {Object} MembershipRequestActionOptions
+ * @property {Array|string|null} requesterIds User ID/s who requested to join the group, if no value is provided, the method will search for all membership requests for that group
+ * @property {Array|number|null} sleep The number of milliseconds to wait before performing an operation for the next requester. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500]
+ */
+
+ /**
+ * Approves membership requests if any
+ * @param {MembershipRequestActionOptions} options Options for performing a membership request action
+ * @returns {Promise>} Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned
+ */
+ async approveGroupMembershipRequests(options = {}) {
+ return await this.client.approveGroupMembershipRequests(this.id._serialized, options);
+ }
+
+ /**
+ * Rejects membership requests if any
+ * @param {MembershipRequestActionOptions} options Options for performing a membership request action
+ * @returns {Promise>} Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned
+ */
+ async rejectGroupMembershipRequests(options = {}) {
+ return await this.client.rejectGroupMembershipRequests(this.id._serialized, options);
+ }
+
+ /**
+ * Makes the bot leave the group
+ * @returns {Promise}
+ */
+ async leave() {
+ await this.client.mPage.evaluate(async chatId => {
+ const chatWid = window.Store.WidFactory.createWid(chatId);
+ const chat = await window.Store.Chat.find(chatWid);
+ return window.Store.GroupUtils.sendExitGroup(chat);
+ }, this.id._serialized);
+ }
+
+}
+
+module.exports = GroupChat;
\ No newline at end of file
diff --git a/src/structures/GroupNotification.js b/src/structures/GroupNotification.js
index bf1b51a..80f1b6d 100644
--- a/src/structures/GroupNotification.js
+++ b/src/structures/GroupNotification.js
@@ -9,7 +9,7 @@
'use strict';
-import Base from './Base.js';
+const Base = require('./Base');
/**
* Represents a GroupNotification on WhatsApp
@@ -47,12 +47,6 @@ class GroupNotification extends Base {
*/
this.timestamp = data.t;
- /**
- * timestamp for ephemeral duration
- * @type {number}
- */
- if (this.type === 'ephemeral') this.ephemeralDuration = Array.isArray(data.templateParams) ? data.templateParams[0] : data.templateParams;
-
/**
* ID for the Chat that this groupNotification was sent for.
*
@@ -65,11 +59,6 @@ class GroupNotification extends Base {
* @type {string}
*/
this.author = typeof (data.author) === 'object' ? data.author._serialized : data.author;
-
- /**
- * @type {Array}
- */
- this.templateParams = Array.isArray(data.templateParams) && data.templateParams || []
/**
* Contact IDs for the users that were affected by this GroupNotification.
@@ -121,4 +110,4 @@ class GroupNotification extends Base {
}
-export default GroupNotification;
\ No newline at end of file
+module.exports = GroupNotification;
\ No newline at end of file
diff --git a/src/structures/Label.js b/src/structures/Label.js
index 4b3b6ff..da97cf6 100644
--- a/src/structures/Label.js
+++ b/src/structures/Label.js
@@ -9,9 +9,9 @@
'use strict';
-import Base from './Base.js';
+const Base = require('./Base');
// eslint-disable-next-line no-unused-vars
-import Chat from './Chat.js';
+const Chat = require('./Chat');
/**
* WhatsApp Business Label information
@@ -56,4 +56,4 @@ class Label extends Base {
}
-export default Label;
\ No newline at end of file
+module.exports = Label;
\ No newline at end of file
diff --git a/src/structures/LinkingMethod.js b/src/structures/LinkingMethod.js
deleted file mode 100644
index b85d686..0000000
--- a/src/structures/LinkingMethod.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * MywaJS 2023
- * re-developed wwebjs
- * using with playwright & wajs
- * contact:
- * wa: 085157489446
- * ig: amirul.dev
- */
-
-class LinkingMethod {
- /**
- *
- * @typedef QR
- * @type {object}
- * @property {number} maxRetries - The maximum number of retries to get the QR code before disconnecting
- *
- * @typedef Phone
- * @type {object}
- * @property {string} number - The phone number to link with. This should be in the format of (e.g. 5521998765432)
- *
- * @typedef LinkingMethodData
- * @type {object}
- * @property {QR} qr - Configuration for QR code linking
- * @property {Phone} phone - Configuration for phone number linking
- *
- * @param {LinkingMethodData} data - Linking method configuration
- */
- constructor(data) {
-
- if (data) this._patch(data);
- }
-
- /**
- *
- * @param {LinkingMethodData} data
- */
- _patch({ phone, qr }) {
- if (qr && phone)
- throw new Error(
- 'Cannot create a link with both QR and phone. Please check the linkingMethod property of the client options.'
- );
- this.qr = qr;
- this.phone = phone;
- }
-
- isPhone() {
- return !!this.phone;
- }
-
- isQR() {
- return !!this.qr;
- }
-}
-
-export default LinkingMethod;
\ No newline at end of file
diff --git a/src/structures/List.js b/src/structures/List.js
index 4591b60..6fb1d32 100644
--- a/src/structures/List.js
+++ b/src/structures/List.js
@@ -9,40 +9,18 @@
'use strict';
-import Util from '../util/Util.js';
-
-/**
- * Section spec used in List constructor
- * @typedef {Object} SectionSpec
- * @property {string=} title - The title of the section, can be empty on the first section only.
- * @property {RowSpec[]} rows - The rows of the section.
- */
-
-/**
- * Row spec used in List constructor
- * @typedef {Object} RowSpec
- * @property {string} title - The text to show on the row.
- * @property {string=} id - Custom ID to set on the row. A random one will be generated if one is not passed.
- * @property {string=} description - Custom description for the row, will appear after clicked in the list response message (appended)
- */
-
-/**
- * Formatted section spec
- * @typedef {Object} FormattedSectionSpec
- * @property {string} title
- * @property {{rowId: string; title: string; description: string}[]} rows
- */
+const Util = require('../util/Util');
/**
* Message type List
*/
class List {
/**
- * @param {string} body - A text body, no media.
- * @param {string} buttonText - The text to put on the click to open button.
- * @param {Array} sections - The sections of the list
- * @param {string?} title - Custom boldfaced title property
- * @param {string?} footer - Custom footer added in a small font to the end of the message
+ * @param {string} body
+ * @param {string} buttonText
+ * @param {Array} sections
+ * @param {string?} title
+ * @param {string?} footer
*/
constructor(body, buttonText, sections, title, footer) {
/**
@@ -80,28 +58,23 @@ class List {
/**
* Creates section array from simple array
- * @param {Array} sections
- * @returns {Array}
+ * @param {Array} sections
+ * @returns {Array}
+ * @example
+ * Input: [{title:'sectionTitle',rows:[{id:'customId', title:'ListItem2', description: 'desc'},{title:'ListItem2'}]}}]
+ * Returns: [{'title':'sectionTitle','rows':[{'rowId':'customId','title':'ListItem1','description':'desc'},{'rowId':'oGSRoD','title':'ListItem2','description':''}]}]
*/
- _format(sections) {
- if(!sections.length) {
- throw '[LT02] List without sections';
- }
- if(sections.length > 1 && sections.filter(section => (typeof section.title == 'undefined' )|| section.title == '' ).length > 1) {
- throw '[LT05] You can\'t have more than one empty title.';
- }
- return sections.map((section, index) => {
- if(!section.rows.length) {
- throw '[LT03] Section without rows';
- }
+ _format(sections){
+ if(!sections.length){throw '[LT02] List without sections';}
+ if(sections.length > 1 && sections.filter(s => typeof s.title == 'undefined').length > 1){throw '[LT05] You can\'t have more than one empty title.';}
+ return sections.map( (section) =>{
+ if(!section.rows.length){throw '[LT03] Section without rows';}
return {
title: section.title ? section.title : undefined,
- rows: section.rows.map((row, rowIndex) => {
- if (!row.title) {
- throw `[LT04] Row without title at section index ${index} and row index ${rowIndex}`;
- }
+ rows: section.rows.map( (row) => {
+ if(!row.title){throw '[LT04] Row without title';}
return {
- rowId: row.id ? row.id : Util.generateHash(8),
+ rowId: row.id ? row.id : Util.generateHash(6),
title: row.title,
description: row.description ? row.description : ''
};
@@ -112,4 +85,4 @@ class List {
}
-export default List;
\ No newline at end of file
+module.exports = List;
\ No newline at end of file
diff --git a/src/structures/Location.js b/src/structures/Location.js
index f359cb8..79af6dd 100644
--- a/src/structures/Location.js
+++ b/src/structures/Location.js
@@ -9,6 +9,14 @@
'use strict';
+/**
+ * Location send options
+ * @typedef {Object} LocationSendOptions
+ * @property {string} [name] Location name
+ * @property {string} [address] Location address
+ * @property {string} [url] URL address to be shown within a location message
+ */
+
/**
* Location information
*/
@@ -16,9 +24,9 @@ class Location {
/**
* @param {number} latitude
* @param {number} longitude
- * @param {?string} description
+ * @param {LocationSendOptions} [options] Location send options
*/
- constructor(latitude, longitude, description) {
+ constructor(latitude, longitude, options = {}) {
/**
* Location latitude
* @type {number}
@@ -33,10 +41,30 @@ class Location {
/**
* Name for the location
- * @type {?string}
+ * @type {string|undefined}
+ */
+ this.name = options.name;
+
+ /**
+ * Location address
+ * @type {string|undefined}
+ */
+ this.address = options.address;
+
+ /**
+ * URL address to be shown within a location message
+ * @type {string|undefined}
+ */
+ this.url = options.url;
+
+ /**
+ * Location full description
+ * @type {string|undefined}
*/
- this.description = description;
+ this.description = this.name && this.address
+ ? `${this.name}\n${this.address}`
+ : this.name || this.address || '';
}
}
-export default Location
\ No newline at end of file
+module.exports = Location;
\ No newline at end of file
diff --git a/src/structures/Message.js b/src/structures/Message.js
index 5547da0..158a87a 100644
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -6,17 +6,17 @@
* wa: 085157489446
* ig: amirul.dev
*/
-"use strict";
-
-import Base from "./Base.js";
-import MessageMedia from "./MessageMedia.js";
-import Location from "./Location.js";
-import Order from "./Order.js";
-import Payment from "./Payment.js";
-import {
- MessageTypes
-} from "../util/Constants.js";
-import PollVote from "./PollVote.js";
+
+'use strict';
+
+const Base = require('./Base');
+const MessageMedia = require('./MessageMedia');
+const Location = require('./Location');
+const Order = require('./Order');
+const Payment = require('./Payment');
+const Reaction = require('./Reaction');
+const {MessageTypes} = require('../util/Constants');
+const {Contact} = require('./Contact');
/**
* Represents a Message on WhatsApp
@@ -25,19 +25,19 @@ import PollVote from "./PollVote.js";
class Message extends Base {
constructor(client, data) {
super(client);
- if (!data.author) data.author = data.from._serialized.endsWith('c.us') ? data.from : data.author;
+
if (data) this._patch(data);
}
_patch(data) {
this._data = data;
-
+
/**
* MediaKey that represents the sticker 'ID'
* @type {string}
*/
this.mediaKey = data.mediaKey;
-
+
/**
* ID that represents the message
* @type {object}
@@ -60,9 +60,7 @@ class Message extends Base {
* Message content
* @type {string}
*/
- this.body = this.hasMedia ?
- data.caption || "" :
- data.body || data.pollName || "";
+ this.body = this.hasMedia ? data.caption || '' : data.body || '';
/**
* Message type
@@ -80,10 +78,7 @@ class Message extends Base {
* ID for the Chat that this message was sent to, except if the message was sent by the current user.
* @type {string}
*/
- this.from =
- typeof data.from === "object" && data.from !== null ?
- data.from._serialized :
- data.from;
+ this.from = (typeof (data.from) === 'object' && data.from !== null) ? data.from._serialized : data.from;
/**
* ID for who this message is for.
@@ -92,31 +87,19 @@ class Message extends Base {
* If the message is sent by another user, it will be the ID for the current user.
* @type {string}
*/
- this.to =
- typeof data.to === "object" && data.to !== null ?
- data.to._serialized :
- data.to;
+ this.to = (typeof (data.to) === 'object' && data.to !== null) ? data.to._serialized : data.to;
/**
* If the message was sent to a group, this field will contain the user that sent the message.
* @type {string}
*/
- this.author =
- typeof data.author === "object" && data.author !== null ?
- data.author._serialized :
- data.author;
+ this.author = (typeof (data.author) === 'object' && data.author !== null) ? data.author._serialized : data.author;
/**
* String that represents from which device type the message was sent
* @type {string}
*/
- this.deviceType =
- typeof data.id.id === "string" && data.id.id.length > 21 ?
- "android" :
- typeof data.id.id === "string" && data.id.id.substring(0, 2) === "3A" ?
- "ios" :
- "web";
-
+ this.deviceType = typeof data.id.id === 'string' && data.id.id.length > 21 ? 'android' : typeof data.id.id === 'string' && data.id.id.substring(0, 2) === '3A' ? 'ios' : 'web';
/**
* Indicates if the message was forwarded
* @type {boolean}
@@ -131,17 +114,11 @@ class Message extends Base {
*/
this.forwardingScore = data.forwardingScore || 0;
- /**
- * Indicates if the message is a group chat
- * @type {boolean}
- */
- this.isGroup = /g.us/.test(data.id.remote || (this.fromMe ? this.to : this.from));
-
/**
* Indicates if the message is a status update
* @type {boolean}
*/
- this.isStatus = data?.isStatusV3 || data.id.remote === "status@broadcast";
+ this.isStatus = data.isStatusV3 || data.id.remote === 'status@broadcast';
/**
* Indicates if the message was starred
@@ -165,10 +142,13 @@ class Message extends Base {
* Indicates if the message was sent as a reply to another message.
* @type {boolean}
*/
- this.hasQuotedMsg =
- data.quotedMsg && data.quotedStanzaID && data.quotedParticipant ?
- true :
- false;
+ this.hasQuotedMsg = data.quotedMsg ? true : false;
+
+ /**
+ * Indicates whether there are reactions to the message
+ * @type {boolean}
+ */
+ this.hasReaction = data.hasReaction ? true : false;
/**
* Indicates the duration of the message in seconds
@@ -180,37 +160,40 @@ class Message extends Base {
* Location information contained in the message, if the message is type "location"
* @type {Location}
*/
- this.location =
- data.type === MessageTypes.LOCATION ?
- new Location(data.lat, data.lng, data.loc) :
- undefined;
+ this.location = (() => {
+ if (data.type !== MessageTypes.LOCATION) {
+ return undefined;
+ }
+ let description;
+ if (data.loc && typeof data.loc === 'string') {
+ let splitted = data.loc.split('\n');
+ description = {
+ name: splitted[0],
+ address: splitted[1],
+ url: data.clientUrl
+ };
+ }
+ return new Location(data.lat, data.lng, description);
+ })();
/**
* List of vCards contained in the message.
* @type {Array}
*/
- this.vCards =
- data.type === MessageTypes.CONTACT_CARD_MULTI ?
- data.vcardList.map((c) => c.vcard) :
- data.type === MessageTypes.CONTACT_CARD ?
- [data.body] :
- [];
+ this.vCards = data.type === MessageTypes.CONTACT_CARD_MULTI ? data.vcardList.map((c) => c.vcard) : data.type === MessageTypes.CONTACT_CARD ? [data.body] : [];
/**
* Group Invite Data
* @type {object}
*/
- this.inviteV4 =
- data.type === MessageTypes.GROUP_INVITE ?
- {
- inviteCode: data.inviteCode,
- inviteCodeExp: data.inviteCodeExp,
- groupId: data.inviteGrp,
- groupName: data.inviteGrpName,
- fromId: data.from?._serialized ? data.from._serialized : data.from,
- toId: data.to?._serialized ? data.to._serialized : data.to,
- } :
- undefined;
+ this.inviteV4 = data.type === MessageTypes.GROUP_INVITE ? {
+ inviteCode: data.inviteCode,
+ inviteCodeExp: data.inviteCodeExp,
+ groupId: data.inviteGrp,
+ groupName: data.inviteGrpName,
+ fromId: '_serialized' in data.from ? data.from._serialized : data.from,
+ toId: '_serialized' in data.to ? data.to._serialized : data.to
+ } : undefined;
/**
* Indicates the mentions in the message body.
@@ -233,7 +216,7 @@ class Message extends Base {
*/
this.token = data.token ? data.token : undefined;
- /**
+ /**
* Indicates whether the message is a Gif
* @type {boolean}
*/
@@ -265,6 +248,16 @@ class Message extends Base {
this.productId = data.productId;
}
+ /** Last edit time */
+ if (data.latestEditSenderTimestampMs) {
+ this.latestEditSenderTimestampMs = data.latestEditSenderTimestampMs;
+ }
+
+ /** Last edit message author */
+ if (data.latestEditMsgKey) {
+ this.latestEditMsgKey = data.latestEditMsgKey;
+ }
+
/**
* Links included in the message.
* @type {Array<{link: string, isSuspicious: boolean}>}
@@ -283,10 +276,7 @@ class Message extends Base {
}
/** Selected List row Id **/
- if (
- data.listResponse &&
- data.listResponse.singleSelectReply.selectedRowId
- ) {
+ if (data.listResponse && data.listResponse.singleSelectReply.selectedRowId) {
this.selectedRowId = data.listResponse.singleSelectReply.selectedRowId;
}
@@ -298,18 +288,19 @@ class Message extends Base {
}
/**
- * reload original message result whatsapp web
- * @returns
+ * 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.
+ * @returns {Promise}
*/
async reload() {
const newData = await this.client.mPage.evaluate((msgId) => {
const msg = window.Store.Msg.get(msgId);
- if (!msg) return null;
+ if(!msg) return null;
return window.WWebJS.getMessageModel(msg);
}, this.id._serialized);
- if (!newData) return null;
-
+ if(!newData) return null;
+
this._patch(newData);
return this;
}
@@ -321,34 +312,26 @@ class Message extends Base {
get rawData() {
return this._data;
}
-
+
/**
- * getchat from the sender of the message
- * @returns
+ * Returns the Chat this message was sent in
+ * @returns {Promise}
*/
getChat() {
return this.client.getChatById(this._getChatId());
}
/**
- * getcontact from both the sender and the quoted
- * @returns
+ * Returns the Contact this message was sent from
+ * @returns {Promise}
*/
getContact() {
return this.client.getContactById(this.author || this.from);
}
/**
- * Returns the Contacts mentioned in this message
- * @returns {Promise