From 2aea280a1a8421c93455835373bddccb281f6ccf Mon Sep 17 00:00:00 2001 From: jonalan Date: Wed, 11 Dec 2024 12:13:49 -0300 Subject: [PATCH] feat: changes and new functions --- README.md | 20 +- docs/getting-started/events.md | 34 ++ docs/getting-started/group_functions.md | 15 + docs/getting-started/retrieving_data.md | 9 + docs/getting-started/start_bot.md | 4 +- src/webpack/@types/API.d.ts | 35 +- src/webpack/api/layes/group.layer.ts | 73 ++-- src/webpack/api/layes/helpers.ts | 36 +- src/webpack/api/layes/retriever.layer.ts | 30 +- src/webpack/api/layes/sender.layes.ts | 25 +- src/webpack/assets/api.js | 12 + .../assets/functions/get-group-participant.js | 46 +++ src/webpack/assets/functions/help/index.js | 3 + src/webpack/assets/functions/help/scope.js | 9 +- .../assets/functions/help/send-exist.js | 127 +++--- src/webpack/assets/functions/help/sleep.js | 7 + .../functions/help/wait-for-selector.js | 30 ++ src/webpack/assets/functions/index.js | 4 + .../load-and-get-all-messages-in-chat.js | 115 ++++++ src/webpack/assets/functions/send-message.js | 224 +++++----- .../assets/functions/set-group-description.js | 2 +- src/webpack/assets/help/filter-object.js | 8 + src/webpack/assets/serializers/index.js | 1 + .../serializers/seialize-group-participant.js | 65 +++ .../assets/serializers/serialize-message.js | 1 + src/webpack/model/enum/functions-layes.ts | 17 + src/webpack/model/enum/index.ts | 2 + src/webpack/model/enum/interface-state.ts | 34 ++ src/webpack/model/interface/index.ts | 2 +- .../model/interface/interface-change.ts | 13 +- src/ws/services/hydra.ts | 4 +- test/dev/bot.ts | 388 ++++++++++-------- test/help.ts | 2 + 33 files changed, 922 insertions(+), 475 deletions(-) create mode 100644 src/webpack/assets/functions/get-group-participant.js create mode 100644 src/webpack/assets/functions/help/sleep.js create mode 100644 src/webpack/assets/functions/help/wait-for-selector.js create mode 100644 src/webpack/assets/functions/load-and-get-all-messages-in-chat.js create mode 100644 src/webpack/assets/serializers/seialize-group-participant.js create mode 100644 src/webpack/model/enum/functions-layes.ts create mode 100644 src/webpack/model/enum/interface-state.ts diff --git a/README.md b/README.md index 8114338..2269349 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ const fs = require('fs'); (async () => { let client; + let checkConnect = false; // start bot service const ev = await hydraBot.initServer(); @@ -104,7 +105,8 @@ const fs = require('fs'); } // Was connected to whatsapp chat - if (conn.connect) { + if (conn.connect && !checkConnect) { + checkConnect = true; client = conn.client; // class client from hydra-bot const getMe = await client.getHost(); const hostNumber = getMe.id._serialized; // number host @@ -539,6 +541,12 @@ const contacts = await client.getAllContacts(); // return whatsapp version const version = await client.getWAVersion(); + +// Load all messages in chat by date +const listMsg = await client.loadAndGetAllMessagesInChat( + '@c.us', + 'YYYY-MM-DD' +); ``` ## Group Management @@ -546,6 +554,16 @@ const version = await client.getWAVersion(); Group number example `-@g.us` or `@g.us` ```javascript +// get all participants in the group +await client + .getGroupParticipant('00000000000-0000000000@g.us') + .then((result) => { + console.log('Participants: ', result); + }) + .catch((error) => { + console.log('Error Participants: ', error); + }); + // Get all Group const allGroups = await client.getAllChatsGroups(); diff --git a/docs/getting-started/events.md b/docs/getting-started/events.md index 868489a..807a448 100644 --- a/docs/getting-started/events.md +++ b/docs/getting-started/events.md @@ -5,6 +5,15 @@ To use these functions, it is necessary to initialize the bot. [**Click here to learn more.**](../Getting%20Started/start_bot.html) +## Events: +The **Hydra-Bot** uses asynchronous events to provide clearer understanding and streamline workflows. +You can listen for information on each aspect separately. +For example: +- Receive information about the **QR code**; +- Monitor the browser interface to determine if the bot is on the chat page, syncing, or facing conflicts, among other states; +- Obtain data about the connection status, such as whether it's **connected** or **disconnected**; +and much more! + ## Summary - [interfaceChange](#interfaceChange) - [qrcode](#qrcode) @@ -19,6 +28,18 @@ To use these functions, it is necessary to initialize the bot. Event triggered when there's a change in the WhatsApp interface ```javascript +// The "interfaceChange" event can return the following interface states: +// QR - QR code page. +// MAIN - Chat page. +// CONNECTION - Connection. +// SYNCING - Loading page, waiting for data from the smartphone. +// OFFLINE - Offline page, when there is no internet connection. +// CONFLICT - Conflict page, when another WhatsApp Web session is open. +// PROXYBLOCK - Blocked page due to proxy restrictions. +// TOS_BLOCK - Blocked page (Terms of Service violation). +// SMB_TOS_BLOCK - Blocked page (Terms of Service violation for SMB users). +// DEPRECATED_VERSION - Deprecated version page. + // The change information can include elements like screen changes or navigation. ev.on("interfaceChange", (change: any) => { // Processes the interface change, like navigation between screens @@ -102,6 +123,19 @@ Event triggered to return the status of each message (e.g., read, delivered, etc ```javascript // This can include data such as delivery and read status, allowing message state tracking. ev.on("newOnAck", async (event) => { + // To monitor the status of each message, the following parameters can be returned: + // SENDER_BACKFILL_SENT: -7 // Message sent after a backfill by the sender. + // INACTIVE_RECEIVED: -6 // Message received but not active. + // CONTENT_UNUPLOADABLE: -5 // Message content could not be uploaded. + // CONTENT_TOO_BIG: -4 // Message content exceeds the size limit. + // CONTENT_GONE: -3 // Message content is no longer available. + // EXPIRED: -2 // Message expired and cannot be delivered. + // FAILED: -1 // Message delivery failed. + // CLOCK: 0 // Message is pending (waiting to be sent). + // SENT: 1 // Message has been sent. + // RECEIVED: 2 // Message has been received. + // READ: 3 // Message has been read. + // PLAYED: 4 // Media message has been played. // Processes the acknowledgment status of the message console.log("Message ack status:", event); }); diff --git a/docs/getting-started/group_functions.md b/docs/getting-started/group_functions.md index b59cf46..23ae063 100644 --- a/docs/getting-started/group_functions.md +++ b/docs/getting-started/group_functions.md @@ -11,6 +11,7 @@ To use these functions, it is necessary to initialize the bot. - [addParticipant](#addparticipant) - [setGroupDescription](#setgroupdescription) - [setGroupImage](#setgroupimage) + - [getGroupParticipant](#getGroupParticipant) ### Retrieve all groups @@ -77,4 +78,18 @@ await client.setGroupImage('00000000000000@g.us', './file.jpg') }); ``` +### getGroupParticipant + +Get all participants in the group + +```javascript +await client + .getGroupParticipant('00000000000-0000000000@g.us') + .then((result) => { + console.log('Participants: ', result); + }) + .catch((error) => { + console.log('Error Participants: ', error); + }); +``` diff --git a/docs/getting-started/retrieving_data.md b/docs/getting-started/retrieving_data.md index ec18b9c..ae98f52 100644 --- a/docs/getting-started/retrieving_data.md +++ b/docs/getting-started/retrieving_data.md @@ -9,6 +9,7 @@ To use these functions, it is necessary to initialize the bot. ## Summary - [getAllContacts](#getallcontacts) - [checkNumber](#checknumber) + - [loadAndGetAllMessagesInChat](#loadAndGetAllMessagesInChat) ### getAllContacts @@ -18,7 +19,15 @@ const contacts = await client.getAllContacts(); ``` ### checkNumber + Check if the number exists ```javascript const result = await client.checkNumber("@c.us"); +``` + +### loadAndGetAllMessagesInChat + +Load all messages in chat by date +```javascript +const result = await client.loadAndGetAllMessagesInChat("@c.us", "YYYY-MM-DD"); ``` \ No newline at end of file diff --git a/docs/getting-started/start_bot.md b/docs/getting-started/start_bot.md index 3753d77..c52d4b6 100644 --- a/docs/getting-started/start_bot.md +++ b/docs/getting-started/start_bot.md @@ -7,6 +7,7 @@ const hydraBot = require('hydra-bot'); (async () => { let client; + let checkConnect = false; // start bot service const ev = await hydraBot.initServer(); @@ -29,7 +30,8 @@ const hydraBot = require('hydra-bot'); } // Was connected to whatsapp chat - if (conn.connect) { + if (conn.connect && !checkConnect) { + checkConnect = true; client = conn.client; // class client from hydra-bot const getMe = await client.getHost(); const hostNumber = getMe.id._serialized; // number host diff --git a/src/webpack/@types/API.d.ts b/src/webpack/@types/API.d.ts index 6d2673e..b65f3e6 100644 --- a/src/webpack/@types/API.d.ts +++ b/src/webpack/@types/API.d.ts @@ -1,5 +1,3 @@ -// Importações organizadas por categoria - // Interfaces import { InterfaceHost, @@ -9,34 +7,29 @@ import { ReactionIntro, } from '../model/interface'; -// Funcionalidades específicas -import { interfaceChange } from '../model/interface/interface-change'; +import { FunctionsLayer, FunctionParameters } from '../model/enum'; -// Declaração da interface API organizada por categoria interface API { - // Mensagens /** * Send a message to a contact or group */ sendMessage: (to: string, body: string, options: object) => Promise; - /** * Serialize a message object */ serializeMessageObj: (msg: object) => Promise; - - // Contatos /** * Returns a list of all contacts */ getAllContacts: () => Promise; - - // Grupos /** * Get all chat groups */ getAllChatsGroups: () => Promise; - + /** + * Get group participants + */ + getGroupParticipant: (groupId: string) => Promise; /** * Creates a new chat group */ @@ -44,7 +37,6 @@ interface API { groupName: string, contacts: string | string[] ) => Promise; - /** * Add participants to a group */ @@ -52,7 +44,6 @@ interface API { groupId: string, contacts: string | string[] ) => Promise; - /** * Set the group description */ @@ -60,29 +51,22 @@ interface API { groupId: string, description: string ) => Promise; - /** * Set the group image */ setGroupImage: (path: object, to: string) => Promise; - - // Host e números /** * Get information about the connected number */ getHost: () => Promise; - /** * Check if a number exists */ checkNumberStatus: (number: string) => Promise; - - // Versão e reações /** * Get the current WhatsApp version */ getWAVersion: () => Promise; - /** * Serialize reactions */ @@ -91,7 +75,6 @@ interface API { collections: object, type: object ) => Promise; - /** * Serialize intro reactions */ @@ -99,6 +82,12 @@ interface API { emoji: object, type: object ) => Promise; + /** + * Load and get all messages in a chat + */ + loadAndGetAllMessagesInChat: (chatId: string, date: string) => Promise; + + [key: string]: (...args: any[]) => any; } // Declaração global organizada @@ -119,5 +108,3 @@ declare global { const API: API; } - -export {}; diff --git a/src/webpack/api/layes/group.layer.ts b/src/webpack/api/layes/group.layer.ts index 5adad25..6067de9 100644 --- a/src/webpack/api/layes/group.layer.ts +++ b/src/webpack/api/layes/group.layer.ts @@ -7,6 +7,7 @@ import { base64MimeType, resizeImg, } from '../../help'; +import { FunctionsLayer } from '../../model/enum'; export class GroupLayer extends ListenerLayer { constructor( @@ -18,15 +19,24 @@ export class GroupLayer extends ListenerLayer { super(page, browser, options, ev); } + /** + * Get group participants + * @param {string} groupId Group id exemple: 000000000000-0000000000@c.us + */ + public async getGroupParticipant(groupId: string) { + return this.handleApiCallParametres( + FunctionsLayer.getGroupParticipant, + groupId + ); + } + /** * Retrieve all groups */ public async getAllChatsGroups() { - return this.handleApiCall( - async () => await API.getAllChatsGroups(), - 'getAllChatsGroups' - ); + return this.handleApiCallParametres(FunctionsLayer.getAllChatsGroups); } + /** * Creates a new chat group * @param {string} groupName Group name @@ -36,19 +46,11 @@ export class GroupLayer extends ListenerLayer { groupName: string, contacts: string | string[] ): Promise { - return new Promise(async (resolve, reject) => { - const result = await this.page - .evaluate( - ({ groupName, contacts }) => API.createGroup(groupName, contacts), - { groupName, contacts } - ) - .catch(); - if (result.error == true) { - return reject(result); - } else { - return resolve(result); - } - }); + return this.handleApiCallParametres( + FunctionsLayer.createGroup, + groupName, + contacts + ); } /** @@ -60,19 +62,11 @@ export class GroupLayer extends ListenerLayer { groupId: string, contacts: string | string[] ): Promise { - return new Promise(async (resolve, reject) => { - const result = await this.page - .evaluate( - ({ groupId, contacts }) => API.addParticipant(groupId, contacts), - { groupId, contacts } - ) - .catch(); - if (result.error == true) { - return reject(result); - } else { - return resolve(result); - } - }); + return this.handleApiCallParametres( + FunctionsLayer.addParticipant, + groupId, + contacts + ); } /** @@ -84,20 +78,11 @@ export class GroupLayer extends ListenerLayer { groupId: string, description: string ): Promise { - return new Promise(async (resolve, reject) => { - const result = await this.page.evaluate( - ({ groupId, description }) => { - return API.setGroupDescription(groupId, description); - }, - { groupId, description } - ); - - if (result.error == true) { - return reject(result); - } else { - return resolve(result); - } - }); + return this.handleApiCallParametres( + FunctionsLayer.setGroupDescription, + groupId, + description + ); } /** diff --git a/src/webpack/api/layes/helpers.ts b/src/webpack/api/layes/helpers.ts index 2320b3e..8c84fcf 100644 --- a/src/webpack/api/layes/helpers.ts +++ b/src/webpack/api/layes/helpers.ts @@ -1,6 +1,7 @@ import { Page, Browser } from 'puppeteer'; import { CreateOptions } from '../../model/interface/'; import { includesMsgErros } from '../../help/includes-msg-erros'; +import { FunctionsLayer, FunctionParameters } from '../../model/enum'; export class HelperLayer { constructor( @@ -11,26 +12,35 @@ export class HelperLayer { ) {} /** - * Wrapper to handle API calls with error handling - * @param evaluateFn Function to evaluate in the page context - * @param methodName Name of the method for error tracking - * @returns The evaluated result + * Function to handle API calls with error handling + * @param functionName - Function name to be called in the API object + * @param args - Function parameters to be passed to the function + * @returns - Returns the result of the function call */ - public async handleApiCall( - evaluateFn: () => Promise, - methodName: string - ): Promise { + public async handleApiCallParametres( + functionName: K, + ...args: FunctionParameters[K] + ): Promise { + const [...params] = args; try { - const result = await this.page.evaluate(evaluateFn); + const result = await this.page.evaluate( + async (fn: string, params: any[]) => { + try { + return await API[fn](...params); + } catch (error: any) { + return { ...error }; + } + }, + functionName, + params + ); + if (result && (result as any).error) { throw result; } + return result; } catch (error: any) { - const checkError = includesMsgErros(error, undefined, methodName); - if (checkError.error) { - throw checkError; - } throw error; } } diff --git a/src/webpack/api/layes/retriever.layer.ts b/src/webpack/api/layes/retriever.layer.ts index d80402d..06d65aa 100644 --- a/src/webpack/api/layes/retriever.layer.ts +++ b/src/webpack/api/layes/retriever.layer.ts @@ -1,6 +1,7 @@ import { GroupLayer } from './group.layer'; import { Page, Browser } from 'puppeteer'; import { CreateOptions, InterfaceHost, Contact } from '../../model/interface'; +import { FunctionsLayer } from '../../model/enum'; export class RetrieverLayer extends GroupLayer { constructor( @@ -12,15 +13,25 @@ export class RetrieverLayer extends GroupLayer { super(page, browser, options, ev); } + /** + * F + * @param chatId - Chat id + * @param date - Date to get messages + * @returns + */ + public async loadAndGetAllMessagesInChat(chatId: string, date: string) { + return this.handleApiCallParametres( + FunctionsLayer.loadAndGetAllMessagesInChat, + chatId, + date + ); + } /** * Returns a list of contacts * @returns List of contacts */ public async getAllContacts(): Promise { - return this.handleApiCall( - async () => await API.getAllContacts(), - 'getAllContacts' - ); + return this.handleApiCallParametres(FunctionsLayer.getAllContacts); } /** @@ -28,7 +39,7 @@ export class RetrieverLayer extends GroupLayer { * @returns Current host device details */ public async getHost(): Promise { - return this.handleApiCall(async () => await API.getHost(), 'getHost'); + return this.handleApiCallParametres(FunctionsLayer.getHost); } /** @@ -36,10 +47,7 @@ export class RetrieverLayer extends GroupLayer { * @returns Current whatsapp version */ public async getWAVersion() { - return this.handleApiCall( - async () => await API.getWAVersion(), - 'getWAVersion' - ); + return this.handleApiCallParametres(FunctionsLayer.getWAVersion); } /** @@ -60,8 +68,8 @@ export class RetrieverLayer extends GroupLayer { * @param {string} number phone number */ public async checkNumber(number: string) { - return await this.page.evaluate( - (number) => API.checkNumberStatus(number), + return this.handleApiCallParametres( + FunctionsLayer.checkNumberStatus, number ); } diff --git a/src/webpack/api/layes/sender.layes.ts b/src/webpack/api/layes/sender.layes.ts index a8d4756..2948fae 100644 --- a/src/webpack/api/layes/sender.layes.ts +++ b/src/webpack/api/layes/sender.layes.ts @@ -10,6 +10,7 @@ import { filenameFromMimeType, } from '../../help'; import * as path from 'path'; +import { FunctionsLayer } from '../../model/enum'; export class SenderLayer extends RetrieverLayer { constructor( @@ -196,23 +197,13 @@ export class SenderLayer extends RetrieverLayer { * @param body text message */ public sendText(to: string, body: string, options: any = {}) { - return new Promise(async (resolve, reject) => { - Object.assign(options, { type: FunctionType.sendText }); - - const result = await this.page - .evaluate( - ({ to, body, options }) => { - return API.sendMessage(to, body, options); - }, - { to, body, options } - ) - .catch(); - if (result.erro == true) { - return reject(result); - } else { - return resolve(result); - } - }); + Object.assign(options, { type: FunctionType.sendText }); + return this.handleApiCallParametres( + FunctionsLayer.sendMessage, + to, + body, + options + ); } /** diff --git a/src/webpack/assets/api.js b/src/webpack/assets/api.js index 48e8346..ab12e83 100644 --- a/src/webpack/assets/api.js +++ b/src/webpack/assets/api.js @@ -18,6 +18,8 @@ import { sendCheckType, returnChat, checkChatExist, + sleep, + waitForSelector, } from './functions/help'; import { @@ -33,6 +35,8 @@ import { getContact, getWAVersion, getAllChatsGroups, + getGroupParticipant, + loadAndGetAllMessagesInChat, } from './functions'; import { @@ -44,6 +48,7 @@ import { serializeMeObj, serializeReactions, serializeIntroReactions, + serializeGroupParticipant, } from './serializers'; import { initParasite } from './init-parasite'; @@ -71,6 +76,11 @@ if (typeof window.API === 'undefined') { window.API.sendCheckType = sendCheckType; window.API.returnChat = returnChat; window.API.checkChatExist = checkChatExist; + window.API.sleep = sleep; + window.API.waitForSelector = waitForSelector; + + // Get Functions + window.API.loadAndGetAllMessagesInChat = loadAndGetAllMessagesInChat; // Send Functions window.API.sendMessage = sendMessage; @@ -88,6 +98,7 @@ if (typeof window.API === 'undefined') { window.API.addParticipant = addParticipant; window.API.setGroupDescription = setGroupDescription; window.API.setGroupImage = setGroupImage; + window.API.getGroupParticipant = getGroupParticipant; // Serialize Functions window.API.serializeMessageObj = serializeMessageObj; @@ -98,4 +109,5 @@ if (typeof window.API === 'undefined') { window.API.serializeMeObj = serializeMeObj; window.API.serializeReactions = serializeReactions; window.API.serializeIntroReactions = serializeIntroReactions; + window.API.serializeGroupParticipant = serializeGroupParticipant; } diff --git a/src/webpack/assets/functions/get-group-participant.js b/src/webpack/assets/functions/get-group-participant.js new file mode 100644 index 0000000..610ebfd --- /dev/null +++ b/src/webpack/assets/functions/get-group-participant.js @@ -0,0 +1,46 @@ +/** + * Function to get group participants + * @param {string} groupId - Group ID + * @returns + */ +export const getGroupParticipant = async (groupId) => { + try { + if (typeof groupId !== 'string') { + throw API.scope(null, true, 404, `Use to groupId string: ${groupId}`); + } + + const chat = await API.sendExist(groupId); + + if (chat && chat.status != 404 && chat.id) { + await Store.Cmd.openChatBottom(chat); + await Store.GrupsConfig.updateReadOnly(chat); + + const classInfoGroup = await API.waitForSelector('._amie'); + if (classInfoGroup) { + classInfoGroup.click(); + } + + const moduleGroup = await Store.GroupMetadata._models.filter( + (e) => e.id._serialized === groupId + ); + + const participants = + moduleGroup.length && moduleGroup[0].participants + ? moduleGroup[0].participants + : undefined; + + if (participants) { + const output = await Promise.all( + participants.map( + async (event) => await API.serializeGroupParticipant(event) + ) + ); + return output; + } + } + + throw API.scope(null, true, 404, `Use to groupId string: ${groupId}`); + } catch (error) { + throw error; + } +}; diff --git a/src/webpack/assets/functions/help/index.js b/src/webpack/assets/functions/help/index.js index cad1786..09d7998 100644 --- a/src/webpack/assets/functions/help/index.js +++ b/src/webpack/assets/functions/help/index.js @@ -10,3 +10,6 @@ export { checkNumberStatus } from './check-number-status'; export { isMD } from './is-md'; export { returnChat } from './return-to-chat'; export { scope } from './scope'; + +export { sleep } from './sleep'; +export { waitForSelector } from './wait-for-selector'; diff --git a/src/webpack/assets/functions/help/scope.js b/src/webpack/assets/functions/help/scope.js index 8344d90..d62ab07 100644 --- a/src/webpack/assets/functions/help/scope.js +++ b/src/webpack/assets/functions/help/scope.js @@ -1,11 +1,4 @@ -export async function scope( - id, - erro, - status, - text = null, - type = null, - body = null -) { +export function scope(id, erro, status, text = null, type = null, body = null) { let e = { to: id, error: erro, diff --git a/src/webpack/assets/functions/help/send-exist.js b/src/webpack/assets/functions/help/send-exist.js index 0cfa2a2..2abf170 100644 --- a/src/webpack/assets/functions/help/send-exist.js +++ b/src/webpack/assets/functions/help/send-exist.js @@ -6,81 +6,80 @@ * @returns */ export const sendExist = async (chatId, returnChat = true, Send = true) => { - const checkContact = API.getContact(chatId); - if (checkContact) { - return await API.returnChat(chatId); - } + try { + const checkContact = API.getContact(chatId); + if (checkContact) { + return await API.returnChat(chatId); + } - const checkType = await API.sendCheckType(chatId); - if (!!checkType && checkType.status === 404) { - return checkType; - } + const checkType = await API.sendCheckType(chatId); + if (!!checkType && checkType.status === 404) { + return checkType; + } - let ck = await window.API.checkNumberStatus(chatId, false); - if ( - (ck.status === 404 && - !chatId.includes('@g.us') && - !chatId.includes('@broadcast')) || - (ck && - ck.text && - typeof ck.text.includes === 'function' && - ck.text.includes('XmppParsingFailure')) - ) { - return API.scope(chatId, true, ck.status, 'The number does not exist'); - } + let ck = await API.checkNumberStatus(chatId, false); + if ( + (ck.status === 404 && + !chatId.includes('@g.us') && + !chatId.includes('@broadcast')) || + (ck && + ck.text && + typeof ck.text.includes === 'function' && + ck.text.includes('XmppParsingFailure')) + ) { + throw API.scope(chatId, true, ck.status, 'The number does not exist'); + } - const chatWid = new window.Store.WidFactory.createWid(chatId); + const chatWid = new Store.WidFactory.createWid(chatId); - let chat = - ck && ck.id && ck.id._serialized - ? await window.API.getChat(ck.id._serialized) - : undefined; + let chat = + ck && ck.id && ck.id._serialized + ? await API.getChat(ck.id._serialized) + : undefined; - if (ck.numberExists && chat === undefined) { - var idUser = new window.Store.UserConstructor(chatId, { - intentionallyUsePrivateConstructor: true, - }); - chat = await window.Store.Chat.find(idUser); - } + if (ck.numberExists && chat === undefined) { + var idUser = new Store.UserConstructor(chatId, { + intentionallyUsePrivateConstructor: true, + }); + chat = await Store.Chat.find(idUser); + } - if (!chat) { - const storeChat = await window.Store.Chat.find(chatWid); - if (storeChat) { - chat = - storeChat && storeChat.id && storeChat.id._serialized - ? await window.API.getChat(storeChat.id._serialized) - : undefined; + if (!chat) { + const storeChat = await Store.Chat.find(chatWid); + if (storeChat) { + chat = + storeChat && storeChat.id && storeChat.id._serialized + ? await API.getChat(storeChat.id._serialized) + : undefined; + } } - } - if (!ck.numberExists && !chat.t && chat.isUser) { - return window.API.scope( - chatId, - true, - ck.status, - 'The number does not exist' - ); - } + if (!ck.numberExists && !chat.t && chat.isUser) { + throw API.scope(chatId, true, ck.status, 'The number does not exist'); + } - if (!ck.numberExists && !chat.t && chat.isGroup) { - return window.API.scope( - chatId, - true, - ck.status, - 'The group number does not exist on your chat list, or it does not exist at all!' - ); - } + if (!ck.numberExists && !chat.t && chat.isGroup) { + throw API.scope( + chatId, + true, + ck.status, + 'The group number does not exist on your chat list, or it does not exist at all!' + ); + } - if (!chat) { - return window.API.scope(chatId, true, 404); - } + if (!chat) { + throw API.scope(chatId, true, 404); + } - if (Send) { - await window.Store.ReadSeen.sendSeen(chat, false); - } + if (Send) { + await Store.ReadSeen.sendSeen(chat, false); + } - if (returnChat) { - return chat; + if (returnChat) { + return chat; + } + return API.scope(chatId, false, 200); + } catch (error) { + throw error; } - return window.API.scope(chatId, false, 200); }; diff --git a/src/webpack/assets/functions/help/sleep.js b/src/webpack/assets/functions/help/sleep.js new file mode 100644 index 0000000..bcce4e0 --- /dev/null +++ b/src/webpack/assets/functions/help/sleep.js @@ -0,0 +1,7 @@ +/** + * Sleep for a specified time + * @param {number} ms - Time in milliseconds + * @returns {Promise} Resolves after the specified time + */ +export const sleep = async (ms) => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/webpack/assets/functions/help/wait-for-selector.js b/src/webpack/assets/functions/help/wait-for-selector.js new file mode 100644 index 0000000..263ced9 --- /dev/null +++ b/src/webpack/assets/functions/help/wait-for-selector.js @@ -0,0 +1,30 @@ +/** + * Waits for a specific DOM element to appear + * @param {string} selector - The CSS selector of the element to wait for + * @param {number} interval - Time in milliseconds between checks (default: 200ms) + * @param {number} maxAttempts - Maximum number of attempts (default: 20) + * @returns {Promise} Resolves with the found DOM element, or rejects if not found + */ +export const waitForSelector = (selector, interval = 200, maxAttempts = 20) => { + return new Promise((resolve, reject) => { + let attempts = 0; + + const pollForElement = () => { + const element = document.querySelector(selector); + if (element) { + resolve(element); + } else if (attempts >= maxAttempts) { + reject( + new Error( + `Element "${selector}" not found after ${maxAttempts} attempts.` + ) + ); + } else { + attempts++; + setTimeout(pollForElement, interval); + } + }; + + pollForElement(); + }); +}; diff --git a/src/webpack/assets/functions/index.js b/src/webpack/assets/functions/index.js index 1f1476c..bb6945d 100644 --- a/src/webpack/assets/functions/index.js +++ b/src/webpack/assets/functions/index.js @@ -10,9 +10,13 @@ export { setGroupDescription } from './set-group-description'; export { setGroupImage } from './set-group-image'; // Get Functions +export { loadAndGetAllMessagesInChat } from './load-and-get-all-messages-in-chat'; + +// Get Host Functions export { getHost } from './get-host'; export { getContact } from './get-contact'; export { getAllChats } from './get-all-chats'; export { getAllContacts } from './get-all-contacts'; export { getWAVersion } from './get-wa-version'; export { getAllChatsGroups } from './get-all-chats-groups'; +export { getGroupParticipant } from './get-group-participant'; diff --git a/src/webpack/assets/functions/load-and-get-all-messages-in-chat.js b/src/webpack/assets/functions/load-and-get-all-messages-in-chat.js new file mode 100644 index 0000000..7810ac2 --- /dev/null +++ b/src/webpack/assets/functions/load-and-get-all-messages-in-chat.js @@ -0,0 +1,115 @@ +/** + * Formats a timestamp to a date string. + * @param {*} timestamp - Timestamp. + * @returns + */ +const formatTimestampToDate = (timestamp) => { + const time = timestamp?.startOfDay ?? timestamp?.t; + if (time) { + const date = new Date(time * 1000); + const formatter = new Intl.DateTimeFormat('en-CA'); + return formatter.format(date); + } + return null; +}; + +/** + * Function to add a new message to a specific date. + * @param {*} date - Date. + * @param {*} output - Output. + * @param {*} newMessage - New message. + */ +const addMessage = (date, newMessage, output) => { + const item = output.find((entry) => entry.date === date); + if (item) { + item.msgList.push(newMessage); + } else { + output.push({ + date: date, + msgList: [newMessage], + }); + } +}; + +/** + * Loads and retrieves all messages in a chat. + * @param {string} chatId - Chat ID. + * @param {string} endDate - Optional end date in format YYYY-MM-DD. + * @returns chat messages + */ +export const loadAndGetAllMessagesInChat = async (chatId, endDate) => { + try { + if (endDate) { + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(endDate)) { + throw API.scope( + null, + true, + 404, + 'Invalid endDate format. Expected YYYY-MM-DD.' + ); + } + } + + const chat = await API.sendExist(chatId); + + if (!chat || chat.status === 404) { + throw new Error('Chat not found'); + } + + const loadMessagesButtonClass = + 'x14m1o6m x126m2zf x1b9z3ur x9f619 x1rg5ohu x1okw0bk x193iq5w x123j3cw xn6708d x10b6aqq x1ye3gou x13a8xbf xdod15v x2b8uid x1lq5wgf xgqcy7u x30kzoy x9jhf4c'; + + const chatScrollClass = + 'x10l6tqk x13vifvy x17qophe xyw6214 x9f619 x78zum5 xdt5ytf xh8yej3 x5yr21d x6ikm8r x1rife3k xjbqb8w x1ewm37j'; + + await Store.Cmd.openChatBottom(chat); + const messages = chat.msgs._models; + + while (!chat.msgs.msgLoadState.noEarlierMsgs) { + const loadButton = document.querySelector( + `button[class="${loadMessagesButtonClass}"]` + ); + const chatScroll = document.querySelector( + `div[class="${chatScrollClass}"]` + ); + if (chatScroll) { + chatScroll.scrollTop = 0; + } + if (loadButton) { + loadButton.click(); + } + await chat.onEmptyMRM(); + await API.sleep(2000); + + const [firstMsg] = messages; + if (endDate && formatTimestampToDate(firstMsg) <= endDate) { + break; + } + } + + const output = []; + + // Use Promise.all with map to handle asynchronous operations + await Promise.all( + Object.keys(messages).map(async (key) => { + if (key === 'remove') return; + const messageObj = messages[key]; + const serializedMessage = await API.serializeMessageObj( + messageObj, + false + ); + const formattedDate = formatTimestampToDate(serializedMessage); + if (endDate && formattedDate <= endDate) { + return; + } + addMessage(formattedDate, serializedMessage, output); + }) + ); + + return output; + } catch (error) { + console.error('Error loading messages:', error); + throw error; + } +}; diff --git a/src/webpack/assets/functions/send-message.js b/src/webpack/assets/functions/send-message.js index 0b24963..71838cb 100644 --- a/src/webpack/assets/functions/send-message.js +++ b/src/webpack/assets/functions/send-message.js @@ -5,132 +5,136 @@ * @param {object} options shipping options */ export async function sendMessage(to, body, options = {}) { - const types = [ - 'sendText', - 'sendAudioBase64', - 'sendImageFromBase64', - 'sendAudio', - 'sendFile', - 'sendImage', - ]; + try { + const types = [ + 'sendText', + 'sendAudioBase64', + 'sendImageFromBase64', + 'sendAudio', + 'sendFile', + 'sendImage', + ]; - let typesObj; - types.reduce( - (a, v) => - (typesObj = { - ...a, - [v]: v, - }), - {} - ); + let typesObj; + types.reduce( + (a, v) => + (typesObj = { + ...a, + [v]: v, + }), + {} + ); - if (!body) { - return API.scope(undefined, true, null, `parameters are missing`); - } + if (!body) { + throw API.scope(to, true, null, `parameters are missing`); + } - if (!options.type || (options.type && !types.includes(options.type))) { - return API.scope( - undefined, - true, - null, - `pass the message type, examples: ${types.join(', ')}` - ); - } + if (!options.type || (options.type && !types.includes(options.type))) { + throw API.scope( + to, + true, + null, + `pass the message type, examples: ${types.join(', ')}` + ); + } - const chat = await API.sendExist(to); - const merge = {}; + const chat = await API.sendExist(to); + const merge = {}; - if (chat && chat.status != 404 && chat.id) { - const newMsgId = await window.API.getNewMessageId(chat.id._serialized); - const fromwWid = await window.Store.MaybeMeUser.getMaybeMeUser(); + if (chat && chat.status != 404 && chat.id) { + const newMsgId = await window.API.getNewMessageId(chat.id._serialized); + const fromwWid = await window.Store.MaybeMeUser.getMaybeMeUser(); - if (options.type === typesObj.sendText) { - merge.type = 'chat'; - } + if (options.type === typesObj.sendText) { + merge.type = 'chat'; + } - if ( - options.type === typesObj.sendAudioBase64 || - options.type === typesObj.sendAudio || - options.type === typesObj.sendFile || - options.type === typesObj.sendImage || - options.type === typesObj.sendImageFromBase64 - ) { - let result = await Store.Chat.find(chat.id); - const mediaBlob = API.base64ToFile(body); - const mc = await API.processFiles(result, mediaBlob); - if (typeof mc === 'object' && mc._models && mc._models[0]) { - const media = mc._models[0]; - let enc, type; + if ( + options.type === typesObj.sendAudioBase64 || + options.type === typesObj.sendAudio || + options.type === typesObj.sendFile || + options.type === typesObj.sendImage || + options.type === typesObj.sendImageFromBase64 + ) { + let result = await Store.Chat.find(chat.id); + const mediaBlob = API.base64ToFile(body); + const mc = await API.processFiles(result, mediaBlob); + if (typeof mc === 'object' && mc._models && mc._models[0]) { + const media = mc._models[0]; + let enc, type; - if ( - options.type === typesObj.sendFile || - options.type === typesObj.sendImage || - options.type === typesObj.sendImageFromBase64 - ) { - type = media.type; - merge.caption = options?.caption; - merge.filename = options?.filename; - enc = await API.encryptAndUploadFile(type, mediaBlob); - } else { - type = 'ptt'; - enc = await API.encryptAndUploadFile(type, mediaBlob); - } + if ( + options.type === typesObj.sendFile || + options.type === typesObj.sendImage || + options.type === typesObj.sendImageFromBase64 + ) { + type = media.type; + merge.caption = options?.caption; + merge.filename = options?.filename; + enc = await API.encryptAndUploadFile(type, mediaBlob); + } else { + type = 'ptt'; + enc = await API.encryptAndUploadFile(type, mediaBlob); + } - if (enc === false) { - return API.scope(chat.id, true, 404, 'Error to encryptAndUploadFile'); - } + if (enc === false) { + throw API.scope(to, true, 404, 'Error to encryptAndUploadFile'); + } - merge.type = type; - merge.duration = media?.__x_mediaPrep?._mediaData?.duration; - merge.mimetype = media.mimetype; - merge.size = media.filesize; - merge.deprecatedMms3Url = enc.url; - merge.directPath = enc.directPath; - merge.encFilehash = enc.encFilehash; - merge.filehash = enc.filehash; - merge.mediaKeyTimestamp = enc.mediaKeyTimestamp; - merge.ephemeralStartTimestamp = enc.mediaKeyTimestamp; - merge.mediaKey = enc.mediaKey; - body = undefined; + merge.type = type; + merge.duration = media?.__x_mediaPrep?._mediaData?.duration; + merge.mimetype = media.mimetype; + merge.size = media.filesize; + merge.deprecatedMms3Url = enc.url; + merge.directPath = enc.directPath; + merge.encFilehash = enc.encFilehash; + merge.filehash = enc.filehash; + merge.mediaKeyTimestamp = enc.mediaKeyTimestamp; + merge.ephemeralStartTimestamp = enc.mediaKeyTimestamp; + merge.mediaKey = enc.mediaKey; + body = undefined; + } } - } - if (!Object.keys(merge).length) { - return API.scope(undefined, true, null, 'Error sending message'); - } + if (!Object.keys(merge).length) { + throw API.scope(to, true, null, 'Error sending message'); + } - const message = window.API.baseSendMessage( - { - to, - body, - newMsgId, - fromwWid, - chat, - }, - merge - ); + const message = window.API.baseSendMessage( + { + to, + body, + newMsgId, + fromwWid, + chat, + }, + merge + ); - try { - const result = ( - await Promise.all(window.Store.addAndSendMsgToChat(chat, message)) - )[1]; - if ( - result === 'success' || - result === 'OK' || - result?.messageSendResult === 'OK' - ) { - return API.scope(newMsgId, false, result, null, options.type, body); + try { + const result = ( + await Promise.all(window.Store.addAndSendMsgToChat(chat, message)) + )[1]; + if ( + result === 'success' || + result === 'OK' || + result?.messageSendResult === 'OK' + ) { + return API.scope(newMsgId, false, result, null, options.type, body); + } + throw result; + } catch (result) { + throw API.scope(newMsgId, true, result, null, options.type, body); + } + } else { + if (!chat.erro) { + chat.erro = true; } - throw result; - } catch (result) { - return API.scope(newMsgId, true, result, null, options.type, body); - } - } else { - if (!chat.erro) { - chat.erro = true; - } - return chat; + return chat; + } + } catch (error) { + throw error; } } diff --git a/src/webpack/assets/functions/set-group-description.js b/src/webpack/assets/functions/set-group-description.js index df5d97e..89f8724 100644 --- a/src/webpack/assets/functions/set-group-description.js +++ b/src/webpack/assets/functions/set-group-description.js @@ -13,7 +13,7 @@ export async function setGroupDescription(groupId, description) { const chat = await API.sendExist(groupId); if (chat && chat.status != 404) { try { - await window.Store.GroupDesc.setGroupDesc(chat, description); + await Store.GroupDesc.setGroupDesc(chat, description); return API.scope( groupId, false, diff --git a/src/webpack/assets/help/filter-object.js b/src/webpack/assets/help/filter-object.js index 3e06722..629de1e 100644 --- a/src/webpack/assets/help/filter-object.js +++ b/src/webpack/assets/help/filter-object.js @@ -124,4 +124,12 @@ export const filterObjects = [ type: 'MaybeMeUser', when: (module) => (module.getMaybeMeUser ? module : null), }, + { + type: 'Cmd', + when: (module) => (module.CmdImpl && module.Cmd ? module.Cmd : null), + }, + { + type: 'GrupsConfig', + when: (module) => (module.updateReadOnly ? module : null), + }, ]; diff --git a/src/webpack/assets/serializers/index.js b/src/webpack/assets/serializers/index.js index 053fd9f..da1b353 100644 --- a/src/webpack/assets/serializers/index.js +++ b/src/webpack/assets/serializers/index.js @@ -8,3 +8,4 @@ export { serializeReactions, serializeIntroReactions, } from './serialize-reactions'; +export { serializeGroupParticipant } from './seialize-group-participant'; diff --git a/src/webpack/assets/serializers/seialize-group-participant.js b/src/webpack/assets/serializers/seialize-group-participant.js new file mode 100644 index 0000000..78d2078 --- /dev/null +++ b/src/webpack/assets/serializers/seialize-group-participant.js @@ -0,0 +1,65 @@ +/** + * Serialize a group participant + * @param {object} participant - Group participant + */ +export const serializeGroupParticipant = async (participant) => { + const idUser = await Store.MaybeMeUser.getMaybeMeUser(); + const isMe = idUser._serialized === participant.id._serialized; + + const { + id = null, + isAdmin = false, + isSuperAdmin = false, + contact = {}, + } = participant; + + const { + verifiedName = null, + verifiedLevel = null, + shortName = null, + pushname = null, + name = null, + isBusiness = null, + isFavorite = null, + isHosted = null, + profilePicThumb = {}, + isAddressBookContact, + } = contact; + + const { + eurl = null, + img = null, + imgFull = null, + previewDirectPath = null, + tag = null, + previewEurl = null, + filehash = null, + fullDirectPath = null, + } = profilePicThumb; + + return { + id, + isAdmin, + isSuperAdmin, + verifiedName, + verifiedLevel, + shortName, + pushname, + name, + isBusiness, + isFavorite, + isHosted, + profilePicThumb: { + eurl, + img, + imgFull, + previewDirectPath, + tag, + previewEurl, + filehash, + fullDirectPath, + }, + isContact: Boolean(isAddressBookContact), + isMe, + }; +}; diff --git a/src/webpack/assets/serializers/serialize-message.js b/src/webpack/assets/serializers/serialize-message.js index 4cb290e..87fb01b 100644 --- a/src/webpack/assets/serializers/serialize-message.js +++ b/src/webpack/assets/serializers/serialize-message.js @@ -48,6 +48,7 @@ export const serializeMessageObj = async (obj, groupInfo = true) => { return { ...window.API.serializeRawObj(obj), id: obj?.id?._serialized, + startOfDay: obj?.startOfDay, msgKey: obj?.id, from: obj?.from?._serialized, quotedParticipant: diff --git a/src/webpack/model/enum/functions-layes.ts b/src/webpack/model/enum/functions-layes.ts new file mode 100644 index 0000000..6d0de33 --- /dev/null +++ b/src/webpack/model/enum/functions-layes.ts @@ -0,0 +1,17 @@ +export enum FunctionsLayer { + getGroupParticipant = 'getGroupParticipant', + getAllChatsGroups = 'getAllChatsGroups', + getAllContacts = 'getAllContacts', + getHost = 'getHost', + getWAVersion = 'getWAVersion', + checkNumberStatus = 'checkNumberStatus', + createGroup = 'createGroup', + addParticipant = 'addParticipant', + setGroupDescription = 'setGroupDescription', + sendMessage = 'sendMessage', + loadAndGetAllMessagesInChat = 'loadAndGetAllMessagesInChat', +} + +export type FunctionParameters = { + [K in keyof typeof API]: Parameters<(typeof API)[K]>; +}; diff --git a/src/webpack/model/enum/index.ts b/src/webpack/model/enum/index.ts index 3944ab9..d15a042 100644 --- a/src/webpack/model/enum/index.ts +++ b/src/webpack/model/enum/index.ts @@ -3,3 +3,5 @@ export { FunctionType } from './functions-type'; export { TypeStatusFind } from './type-status-find'; export { InterfaceMode } from './interface-mode'; export { ReactionType } from './reaction-type'; +export { InterfaceState } from './interface-state'; +export { FunctionsLayer, FunctionParameters } from './functions-layes'; diff --git a/src/webpack/model/enum/interface-state.ts b/src/webpack/model/enum/interface-state.ts new file mode 100644 index 0000000..224d464 --- /dev/null +++ b/src/webpack/model/enum/interface-state.ts @@ -0,0 +1,34 @@ +export enum InterfaceState { + /** + * When the whatsapp web is connecting to whatsapp server. + */ + CONNECTING = 'CONNECTING', + /** + * When the whatsapp web is ready. + */ + NORMAL = 'NORMAL', + /** + * When there are no internet. + */ + OFFLINE = 'OFFLINE', + /** + * When the whatsapp web page is loading. + */ + OPENING = 'OPENING', + /** + * When the whatsapp web is connecting to smartphone after QR code scan. + */ + PAIRING = 'PAIRING', + /** + * When the whatsapp web is syncing messages with smartphone after a disconnection. + */ + RESUMING = 'RESUMING', + /** + * When the whatsapp web is syncing messages with smartphone. + */ + SYNCING = 'SYNCING', + /** + * When the whatsapp web couldn't connect to smartphone. + */ + TIMEOUT = 'TIMEOUT', +} diff --git a/src/webpack/model/interface/index.ts b/src/webpack/model/interface/index.ts index bd5e233..098bc93 100644 --- a/src/webpack/model/interface/index.ts +++ b/src/webpack/model/interface/index.ts @@ -1,6 +1,6 @@ export { PuppeteerOptions, CreateOptions, defaultConfig } from './config-api'; export { SendOptions } from './send-layer'; -export { interfaceChange } from './interface-change'; +export { InterfaceChange } from './interface-change'; export { InterfaceQrcode } from './qrcode'; export { InterfaceScope } from './scope'; export { InterfaceHost } from './host'; diff --git a/src/webpack/model/interface/interface-change.ts b/src/webpack/model/interface/interface-change.ts index 10df123..ab739ea 100644 --- a/src/webpack/model/interface/interface-change.ts +++ b/src/webpack/model/interface/interface-change.ts @@ -1,15 +1,20 @@ -import { InterfaceMode } from '../enum'; +import { InterfaceMode, InterfaceState } from '../enum'; import { ReactionIntro, InterfaceQrcode } from '../interface'; + interface ResultInfo { - displayInfo: string; + displayInfo: InterfaceState; mode: InterfaceMode; - info: string; + info: InterfaceState; } -export interface interfaceChange { +export interface InterfaceChange { onType: string; session: string; result: Partial & Partial & Partial; + // Qrcode + error?: boolean; + qrcode?: string; + base64Image?: string; } diff --git a/src/ws/services/hydra.ts b/src/ws/services/hydra.ts index d6d3629..72db9dd 100644 --- a/src/ws/services/hydra.ts +++ b/src/ws/services/hydra.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { initServer, OnMode, - interfaceChange, + InterfaceChange, InterfaceQrcode, } from '../../index'; import mime from 'mime-types'; @@ -38,7 +38,7 @@ async function Webhook(options: any, info: any) { const ev = await initServer(objOptions); let client: any; - ev.on(OnMode.interfaceChange, (change: interfaceChange) => { + ev.on(OnMode.interfaceChange, (change: InterfaceChange) => { Webhook(objOptions, change); }); diff --git a/test/dev/bot.ts b/test/dev/bot.ts index b0c98d1..9e3ea37 100644 --- a/test/dev/bot.ts +++ b/test/dev/bot.ts @@ -3,7 +3,9 @@ import { OnMode, WebPack, CallbackOnStatus, - interfaceChange, + InterfaceChange, + InterfaceMode, + InterfaceState, } from '../../dist'; import { pathFile, hydraBotTestFunctions } from '../help'; @@ -12,183 +14,221 @@ import mime from 'mime-types'; import fs from 'fs'; (async () => { - let client: WebPack; - - // start bot service - const ev: CallbackOnStatus = await initServer({ - puppeteerOptions: { - headless: false, // Open chrome browser in interface - devtools: true, // Inspect the html element through the console - }, - timeAutoClose: 0, // 0 = disabled, 1000 = 1 second, 2000 = 2 seconds, etc - printQRInTerminal: true, // Print QR code in terminal - }); - - // return to current whatsapp interface - ev.on(OnMode.interfaceChange, (change: any) => { - if (!hydraBotTestFunctions.interfaceChange) return; - console.log('interfaceChange: ', change); - }); - - // return qrcode parameters - ev.on(OnMode.qrcode, (qrcode: interfaceChange) => { - if (!hydraBotTestFunctions.qrcode) return; - console.log('qrcode: ', qrcode); - }); - - // return connection information - ev.on(OnMode.connection, async (conn: any) => { - // browser information! - if (conn.statusFind === 'browser') { - console.log('info Browser: ', conn.text); - } - - // Was connected to whatsapp chat - if (conn.connect) { - client = conn.client; // class client from hydra-bot - const getMe = await client.getHost(); - const hostNumber = getMe.id._serialized; // number host - console.log('Host Number: ', hostNumber); - - // Get Functions - if (hydraBotTestFunctions.getAllChatsGroups) { - // get all groups - await client - .getAllChatsGroups() - .then((result) => { - console.log('Groups: ', result); - }) - .catch((error) => { - console.log('Error Groups: ', error); - }); - } - - if (hydraBotTestFunctions.getWAVersion) { - // get version whatsapp - await client - .getWAVersion() - .then((result) => { - console.log('Version: ', result); - }) - .catch((error) => { - console.log('Error Version: ', error); - }); - } - - if (hydraBotTestFunctions.getHost) { - // get host information - await client - .getHost() - .then((result) => { - console.log('Host: ', result); - }) - .catch((error) => { - console.log('Error Host: ', error); - }); + try { + let client: WebPack; + let checkConnect = false; + + // start bot service + const ev: CallbackOnStatus = await initServer({ + session: 'session', // session name + puppeteerOptions: { + headless: false, // Open chrome browser in interface + devtools: true, // Inspect the html element through the console + }, + timeAutoClose: 0, // 0 = disabled, 1000 = 1 second, 2000 = 2 seconds, etc + printQRInTerminal: true, // Print QR code in terminal + }); + + // return to current whatsapp interface + ev.on(OnMode.interfaceChange, (change: InterfaceChange) => { + if (!hydraBotTestFunctions.interfaceChange) return; + console.log('interfaceChange: ', change); + const { mode, displayInfo } = change.result; + const modeTyped = mode as InterfaceMode; + const displayInfoTyped = displayInfo as InterfaceState; + + console.log('Mode: ', modeTyped); + console.log('DisplayInfo: ', displayInfoTyped); + }); + + // return qrcode parameters + ev.on(OnMode.qrcode, (qrcode: InterfaceChange) => { + if (!hydraBotTestFunctions.qrcode) return; + console.log('qrcode: ', qrcode); + }); + + // return connection information + ev.on(OnMode.connection, async (conn: any) => { + // browser information! + if (conn.statusFind === 'browser') { + console.log('info Browser: ', conn.text); } - if (hydraBotTestFunctions.getAllContact) { - // get all contacts - await client - .getAllContacts() - .then((result) => { - console.log('Contacts: ', result); - }) - .catch((error) => { - console.log('Error Contacts: ', error); - }); + console.log('Connection: ', conn.connect); + + // Was connected to whatsapp chat + if (conn.connect && !checkConnect) { + checkConnect = true; + client = conn.client; // class client from hydra-bot + const getMe = await client.getHost(); + const hostNumber = getMe.id._serialized; // number host + console.log('Host Number: ', hostNumber); + + if (hydraBotTestFunctions.loadAndGetAllMessagesInCha) { + // Load all messages in chat by date + await client + .loadAndGetAllMessagesInChat('00000000000@c.us', 'YYYY-MM-DD') + .then((result) => { + console.log('Messages: ', result); + }) + .catch((error) => { + console.log('Error Messages: ', error); + }); + } + // Get Functions + if (hydraBotTestFunctions.getGroupParticipant) { + // get all participants in the group + await client + .getGroupParticipant('00000000000-0000000000@g.us') + .then((result) => { + console.log('Participants: ', result); + }) + .catch((error) => { + console.log('Error Participants: ', error); + }); + } + + if (hydraBotTestFunctions.getAllChatsGroups) { + // get all groups + await client + .getAllChatsGroups() + .then((result) => { + console.log('Groups: ', result); + }) + .catch((error) => { + console.log('Error Groups: ', error); + }); + } + + if (hydraBotTestFunctions.getWAVersion) { + // get version whatsapp + await client + .getWAVersion() + .then((result) => { + console.log('Version: ', result); + }) + .catch((error) => { + console.log('Error Version: ', error); + }); + } + + if (hydraBotTestFunctions.getHost) { + // get host information + await client + .getHost() + .then((result) => { + console.log('Host: ', result); + }) + .catch((error) => { + console.log('Error Host: ', error); + }); + } + + if (hydraBotTestFunctions.getAllContact) { + // get all contacts + await client + .getAllContacts() + .then((result) => { + console.log('Contacts: ', result); + }) + .catch((error) => { + console.log('Error Contacts: ', error); + }); + } + + // Send Functions + if (hydraBotTestFunctions.sendText) { + // send message to host example + await client + .sendText('557599998584545@c.us', 'A message sent by hydra-bot') + .then((result) => { + console.log('Result sucess menssage send:', result); + }) + .catch((error) => { + console.log('Result error message send:', error); + }); + } + + if (hydraBotTestFunctions.sendImage) { + const imgSendPath = pathFile('files', 'img.jpg'); + // Send image message + await client + .sendImage(hostNumber, imgSendPath, { + caption: 'image text', + }) + .then((result) => { + console.log('Result sucess image send:', result); + }) + .catch((error) => { + console.log('Result error image send:', error); + }); + } } - - // Send Functions - if (hydraBotTestFunctions.sendText) { - // send message to host example - await client - .sendText(hostNumber, 'A message sent by hydra-bot') - .then((result) => { - console.log('Result sucess menssage send:', result); - }) - .catch((error) => { - console.log('Result error message send:', error); - }); + }); + + // receive new messages reactions emoji + ev.on(OnMode.onReactionMessage, async (reaction: any) => { + if (!hydraBotTestFunctions.onReactionMessage) return; + console.log('ReactionMessage: ', reaction); + }); + + // receive new message intro reactions emoji + ev.on(OnMode.onIntroReactionMessage, async (reaction: InterfaceChange) => { + if (!hydraBotTestFunctions.onIntroReactionMessage) return; + console.log('IntroReactionMessage: ', reaction.result.type); + }); + + // receive delete messages + ev.on(OnMode.newDeleteMessage, async (message: any) => { + if (!hydraBotTestFunctions.newDeleteMessage) return; + console.log(`Delete message`, message); + }); + + // receive edit messages + ev.on(OnMode.newEditMessage, async (message: any) => { + if (!hydraBotTestFunctions.newEditMessage) return; + console.log(`Edite message`, message); + }); + + // return receive new messages + ev.on(OnMode.newMessage, async (newMsg: any) => { + if (!hydraBotTestFunctions.newMessage) return; + + // when is it received + if (!newMsg.result.fromMe) { + // console.log('NewMessageReceived: ', newMsg.result); } - - if (hydraBotTestFunctions.sendImage) { - const imgSendPath = pathFile('files', 'img.jpg'); - // Send image message - await client - .sendImage(hostNumber, imgSendPath, { - caption: 'image text', - }) - .then((result) => { - console.log('Result sucess image send:', result); - }) - .catch((error) => { - console.log('Result error image send:', error); + // when is it sent + if (newMsg.result.fromMe) { + // Message sent + console.log('NewMessageSent: ', newMsg.result); + // dowload files + if (newMsg.result.isMedia) { + const buffer = await client.decryptFile(newMsg.result); + // At this point you can do whatever you want with the buffer + // Most likely you want to write it into a file + const fileName = `${newMsg.result.id}.${mime.extension( + newMsg.result.mimetype + )}`; + const file = pathFile('dowload', fileName); + fs.writeFile(file, buffer, (err) => { + if (err) { + console.log(err); + } }); + } } - } - }); - - // receive new messages reactions emoji - ev.on(OnMode.onReactionMessage, async (reaction: any) => { - if (!hydraBotTestFunctions.onReactionMessage) return; - console.log('ReactionMessage: ', reaction); - }); - - // receive new message intro reactions emoji - ev.on(OnMode.onIntroReactionMessage, async (reaction: interfaceChange) => { - if (!hydraBotTestFunctions.onIntroReactionMessage) return; - console.log('IntroReactionMessage: ', reaction.result.type); - }); - - // receive delete messages - ev.on(OnMode.newDeleteMessage, async (message: any) => { - if (!hydraBotTestFunctions.newDeleteMessage) return; - console.log(`Delete message`, message); - }); - - // receive edit messages - ev.on(OnMode.newEditMessage, async (message: any) => { - if (!hydraBotTestFunctions.newEditMessage) return; - console.log(`Edite message`, message); - }); - - // return receive new messages - ev.on(OnMode.newMessage, async (newMsg: any) => { - if (!hydraBotTestFunctions.newMessage) return; - - // when is it received - if (!newMsg.result.fromMe) { - // console.log('NewMessageReceived: ', newMsg.result); - } - // when is it sent - if (newMsg.result.fromMe) { - // Message sent - console.log('NewMessageSent: ', newMsg.result); - // dowload files - if (newMsg.result.isMedia) { - const buffer = await client.decryptFile(newMsg.result); - // At this point you can do whatever you want with the buffer - // Most likely you want to write it into a file - const fileName = `${newMsg.result.id}.${mime.extension( - newMsg.result.mimetype - )}`; - const file = pathFile('dowload', fileName); - fs.writeFile(file, buffer, (err) => { - if (err) { - console.log(err); - } - }); - } - } - }); - - // returns the status of each message - ev.on(OnMode.newOnAck, async (event: any) => { - if (!hydraBotTestFunctions.newOnAck) return; - console.log('id Message: ', event.result.id._serialized); // message id - console.log('Status Message: ', event.result.ack); // -7 = MD_DOWNGRADE, -6 = INACTIVE, -5 = CONTENT_UNUPLOADABLE, -4 = CONTENT_TOO_BIG, -3 = CONTENT_GONE, -2 = EXPIRED, -1 = FAILED, 0 = CLOCK, 1 = SENT, 2 = RECEIVED, 3 = READ, 4 = PLAYED - console.log('From Message: ', event.result.from); // from message - console.log('To Message: ', event.result.to); // to message - }); + }); + + // returns the status of each message + ev.on(OnMode.newOnAck, async (event: any) => { + if (!hydraBotTestFunctions.newOnAck) return; + console.log('id Message: ', event.result.id._serialized); // message id + console.log('Status Message: ', event.result.ack); // -7 = MD_DOWNGRADE, -6 = INACTIVE, -5 = CONTENT_UNUPLOADABLE, -4 = CONTENT_TOO_BIG, -3 = CONTENT_GONE, -2 = EXPIRED, -1 = FAILED, 0 = CLOCK, 1 = SENT, 2 = RECEIVED, 3 = READ, 4 = PLAYED + console.log('From Message: ', event.result.from); // from message + console.log('To Message: ', event.result.to); // to message + }); + } catch (error) { + console.log('Error: ', error); + } })(); diff --git a/test/help.ts b/test/help.ts index 6374034..aae2bb3 100644 --- a/test/help.ts +++ b/test/help.ts @@ -19,6 +19,8 @@ export const hydraBotTestFunctions = { getHost: false, getWAVersion: false, getAllChatsGroups: false, + getGroupParticipant: false, + loadAndGetAllMessagesInCha: false, // Events newOnAck: false,