From b5683aed2afe422623086f6672b96d55e6f1b8a5 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Thu, 27 Jun 2024 17:49:48 -0300 Subject: [PATCH] chore: Update 'baileys' dependency to use 'EvolutionAPI' repository The 'baileys' dependency in the 'package.json' file has been updated to use the 'EvolutionAPI' repository instead of 'WhiskeySockets'. This change ensures that the latest version of 'baileys' is used, which includes improvements in group message sending and cache utilization. Modified files: - package.json --- CHANGELOG.md | 1 + package.json | 2 +- .../channels/whatsapp.baileys.service.ts | 444 +++++++----------- src/utils/use-multi-file-auth-state-prisma.ts | 19 +- 4 files changed, 188 insertions(+), 278 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7243ea6e..9cd44e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * Now in typebot we wait until the terminal block to accept the user's message, if it arrives before the block is sent, it is ignored * Correction of audio sending, now we can speed it up and have the audio wireframe * Reply with media message on Chatwoot +* improvements in sending status and groups ### Break changes diff --git a/package.json b/package.json index 061878fb..7d027aa1 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@sentry/node": "^7.59.2", "amqplib": "^0.10.3", "axios": "^1.6.5", - "baileys": "github:WhiskeySockets/Baileys", + "baileys": "github:EvolutionAPI/Baileys", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index c0b123eb..3383b2a6 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -22,6 +22,7 @@ import makeWASocket, { GroupParticipant, isJidBroadcast, isJidGroup, + isJidNewsletter, isJidUser, makeCacheableSignalKeyStore, MessageUpsertType, @@ -506,10 +507,149 @@ export class BaileysStartupService extends ChannelStartupService { } if (db.SAVE_DATA.INSTANCE && db.ENABLED) { - return await useMultiFileAuthStatePrisma(this.instance.id); + return await useMultiFileAuthStatePrisma(this.instance.id, this.cache); } } + private async createClient(number?: string): Promise { + this.instance.authState = await this.defineAuthState(); + + const session = this.configService.get('CONFIG_SESSION_PHONE'); + + let browserOptions = {}; + + if (number || this.phoneNumber) { + this.phoneNumber = number; + + this.logger.info(`Phone number: ${number}`); + } else { + const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; + browserOptions = { browser }; + + this.logger.info(`Browser: ${browser}`); + } + + let version; + let log; + + if (session.VERSION) { + version = session.VERSION.split('.'); + log = `Baileys version env: ${version}`; + } else { + const baileysVersion = await fetchLatestBaileysVersion(); + version = baileysVersion.version; + log = `Baileys version: ${version}`; + } + + this.logger.info(log); + + this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`); + + let options; + + if (this.localProxy.enabled) { + this.logger.info('Proxy enabled: ' + this.localProxy?.host); + + if (this.localProxy?.host?.includes('proxyscrape')) { + try { + const response = await axios.get(this.localProxy?.host); + const text = response.data; + const proxyUrls = text.split('\r\n'); + const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); + const proxyUrl = 'http://' + proxyUrls[rand]; + options = { + agent: makeProxyAgent(proxyUrl), + fetchAgent: makeProxyAgent(proxyUrl), + }; + } catch (error) { + this.localProxy.enabled = false; + } + } else { + options = { + agent: makeProxyAgent({ + host: this.localProxy.host, + port: this.localProxy.port, + protocol: this.localProxy.protocol, + username: this.localProxy.username, + password: this.localProxy.password, + }), + fetchAgent: makeProxyAgent({ + host: this.localProxy.host, + port: this.localProxy.port, + protocol: this.localProxy.protocol, + username: this.localProxy.username, + password: this.localProxy.password, + }), + }; + } + } + + const socketConfig: UserFacingSocketConfig = { + ...options, + auth: { + creds: this.instance.authState.state.creds, + keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), + }, + logger: P({ level: this.logBaileys }), + printQRInTerminal: false, + ...browserOptions, + version, + markOnlineOnConnect: this.localSettings.alwaysOnline, + retryRequestDelayMs: 350, + maxMsgRetryCount: 4, + fireInitQueries: true, + connectTimeoutMs: 20_000, + keepAliveIntervalMs: 30_000, + qrTimeout: 45_000, + defaultQueryTimeoutMs: undefined, + emitOwnEvents: false, + shouldIgnoreJid: (jid) => { + const isGroupJid = this.localSettings.groupsIgnore && isJidGroup(jid); + const isBroadcast = !this.localSettings.readStatus && isJidBroadcast(jid); + const isNewsletter = isJidNewsletter(jid); + + return isGroupJid || isBroadcast || isNewsletter; + }, + msgRetryCounterCache: this.msgRetryCounterCache, + getMessage: async (key) => (await this.getMessage(key)) as Promise, + generateHighQualityLinkPreview: true, + syncFullHistory: this.localSettings.syncFullHistory, + shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => { + return this.historySyncNotification(msg); + }, + userDevicesCache: this.userDevicesCache, + transactionOpts: { maxCommitRetries: 5, delayBetweenTriesMs: 2500 }, + patchMessageBeforeSending(message) { + if ( + message.deviceSentMessage?.message?.listMessage?.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST + ) { + message = JSON.parse(JSON.stringify(message)); + + message.deviceSentMessage.message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; + } + + if (message.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) { + message = JSON.parse(JSON.stringify(message)); + + message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; + } + + return message; + }, + forceGroupsPrekeys: false, + }; + + this.endSession = false; + + this.client = makeWASocket(socketConfig); + + this.eventHandler(); + + this.phoneNumber = number; + + return this.client; + } + public async connectToWhatsapp(number?: string): Promise { try { this.loadWebhook(); @@ -520,142 +660,7 @@ export class BaileysStartupService extends ChannelStartupService { this.loadSqs(); this.loadProxy(); - this.instance.authState = await this.defineAuthState(); - - const session = this.configService.get('CONFIG_SESSION_PHONE'); - - let browserOptions = {}; - - if (number || this.phoneNumber) { - this.phoneNumber = number; - - this.logger.info(`Phone number: ${number}`); - } else { - const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; - browserOptions = { browser }; - - this.logger.info(`Browser: ${browser}`); - } - - let version; - let log; - - if (session.VERSION) { - version = session.VERSION.split('.'); - log = `Baileys version env: ${version}`; - } else { - const baileysVersion = await fetchLatestBaileysVersion(); - version = baileysVersion.version; - log = `Baileys version: ${version}`; - } - - this.logger.info(log); - - this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`); - - let options; - - if (this.localProxy.enabled) { - this.logger.info('Proxy enabled: ' + this.localProxy?.host); - - if (this.localProxy?.host?.includes('proxyscrape')) { - try { - const response = await axios.get(this.localProxy?.host); - const text = response.data; - const proxyUrls = text.split('\r\n'); - const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); - const proxyUrl = 'http://' + proxyUrls[rand]; - options = { - agent: makeProxyAgent(proxyUrl), - fetchAgent: makeProxyAgent(proxyUrl), - }; - } catch (error) { - this.localProxy.enabled = false; - } - } else { - options = { - agent: makeProxyAgent({ - host: this.localProxy.host, - port: this.localProxy.port, - protocol: this.localProxy.protocol, - username: this.localProxy.username, - password: this.localProxy.password, - }), - fetchAgent: makeProxyAgent({ - host: this.localProxy.host, - port: this.localProxy.port, - protocol: this.localProxy.protocol, - username: this.localProxy.username, - password: this.localProxy.password, - }), - }; - } - } - - const socketConfig: UserFacingSocketConfig = { - ...options, - auth: { - creds: this.instance.authState.state.creds, - keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), - }, - logger: P({ level: this.logBaileys }), - printQRInTerminal: false, - ...browserOptions, - version, - markOnlineOnConnect: this.localSettings.alwaysOnline, - retryRequestDelayMs: 350, - maxMsgRetryCount: 4, - fireInitQueries: true, - connectTimeoutMs: 20_000, - keepAliveIntervalMs: 30_000, - qrTimeout: 45_000, - defaultQueryTimeoutMs: undefined, - emitOwnEvents: false, - shouldIgnoreJid: (jid) => { - const isGroupJid = this.localSettings.groupsIgnore && isJidGroup(jid); - const isBroadcast = !this.localSettings.readStatus && isJidBroadcast(jid); - const isNewsletter = jid.includes('newsletter'); - - return isGroupJid || isBroadcast || isNewsletter; - }, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, - syncFullHistory: this.localSettings.syncFullHistory, - shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => { - return this.historySyncNotification(msg); - }, - userDevicesCache: this.userDevicesCache, - transactionOpts: { maxCommitRetries: 5, delayBetweenTriesMs: 2500 }, - patchMessageBeforeSending(message) { - if ( - message.deviceSentMessage?.message?.listMessage?.listType === - proto.Message.ListMessage.ListType.PRODUCT_LIST - ) { - message = JSON.parse(JSON.stringify(message)); - - message.deviceSentMessage.message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - if (message.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) { - message = JSON.parse(JSON.stringify(message)); - - message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - return message; - }, - }; - - this.endSession = false; - - this.client = makeWASocket(socketConfig); - - this.eventHandler(); - - this.phoneNumber = number; - - return this.client; + return await this.createClient(number); } catch (error) { this.logger.error(error); throw new InternalServerErrorException(error?.toString()); @@ -664,132 +669,7 @@ export class BaileysStartupService extends ChannelStartupService { public async reloadConnection(): Promise { try { - this.instance.authState = await this.defineAuthState(); - - const session = this.configService.get('CONFIG_SESSION_PHONE'); - - let browserOptions = {}; - - if (this.phoneNumber) { - this.logger.info(`Phone number: ${this.phoneNumber}`); - } else { - const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; - browserOptions = { browser }; - - this.logger.info(`Browser: ${browser}`); - } - - let version; - let log; - - if (session.VERSION) { - version = session.VERSION.split('.'); - log = `Baileys version env: ${version}`; - } else { - const baileysVersion = await fetchLatestBaileysVersion(); - version = baileysVersion.version; - log = `Baileys version: ${version}`; - } - - this.logger.info(log); - - let options; - - if (this.localProxy.enabled) { - this.logger.info('Proxy enabled: ' + this.localProxy?.host); - - if (this.localProxy?.host?.includes('proxyscrape')) { - try { - const response = await axios.get(this.localProxy?.host); - const text = response.data; - const proxyUrls = text.split('\r\n'); - const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); - const proxyUrl = 'http://' + proxyUrls[rand]; - options = { - agent: makeProxyAgent(proxyUrl), - fetchAgent: makeProxyAgent(proxyUrl), - }; - } catch (error) { - this.localProxy.enabled = false; - } - } else { - options = { - agent: makeProxyAgent({ - host: this.localProxy.host, - port: this.localProxy.port, - protocol: this.localProxy.protocol, - username: this.localProxy.username, - password: this.localProxy.password, - }), - fetchAgent: makeProxyAgent({ - host: this.localProxy.host, - port: this.localProxy.port, - protocol: this.localProxy.protocol, - username: this.localProxy.username, - password: this.localProxy.password, - }), - }; - } - } - - const socketConfig: UserFacingSocketConfig = { - ...options, - auth: { - creds: this.instance.authState.state.creds, - keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), - }, - logger: P({ level: this.logBaileys }), - printQRInTerminal: false, - ...browserOptions, - version, - markOnlineOnConnect: this.localSettings.alwaysOnline, - retryRequestDelayMs: 350, - maxMsgRetryCount: 4, - fireInitQueries: true, - connectTimeoutMs: 20_000, - keepAliveIntervalMs: 30_000, - qrTimeout: 45_000, - defaultQueryTimeoutMs: undefined, - emitOwnEvents: false, - shouldIgnoreJid: (jid) => { - const isGroupJid = this.localSettings.groupsIgnore && isJidGroup(jid); - const isBroadcast = !this.localSettings.readStatus && isJidBroadcast(jid); - const isNewsletter = jid.includes('newsletter'); - - return isGroupJid || isBroadcast || isNewsletter; - }, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, - syncFullHistory: this.localSettings.syncFullHistory, - shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => { - return this.historySyncNotification(msg); - }, - userDevicesCache: this.userDevicesCache, - transactionOpts: { maxCommitRetries: 5, delayBetweenTriesMs: 2500 }, - patchMessageBeforeSending(message) { - if ( - message.deviceSentMessage?.message?.listMessage?.listType === - proto.Message.ListMessage.ListType.PRODUCT_LIST - ) { - message = JSON.parse(JSON.stringify(message)); - - message.deviceSentMessage.message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - if (message.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) { - message = JSON.parse(JSON.stringify(message)); - - message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - return message; - }, - }; - - this.client = makeWASocket(socketConfig); - - return this.client; + return await this.createClient(this.phoneNumber); } catch (error) { this.logger.error(error); throw new InternalServerErrorException(error?.toString()); @@ -1747,11 +1627,13 @@ export class BaileysStartupService extends ChannelStartupService { quoted, }; - if (participants) - option.cachedGroupMetadata = async () => { - return { participants: participants as GroupParticipant[] }; - }; - else option.cachedGroupMetadata = this.getGroupMetadataCache; + if (isJidGroup(sender)) { + if (participants) + option.cachedGroupMetadata = async () => { + return { participants: participants as GroupParticipant[] }; + }; + else option.cachedGroupMetadata = this.getGroupMetadataCache; + } if (ephemeralExpiration) option.ephemeralExpiration = ephemeralExpiration; @@ -1968,7 +1850,6 @@ export class BaileysStartupService extends ChannelStartupService { return jid; }); } - // console.log('group.participants', group.participants.length); // const batchSize = 200; @@ -1985,7 +1866,16 @@ export class BaileysStartupService extends ChannelStartupService { // let msgId: string | null = null; // if (firstBatch) { - // firstMessage = await this.sendMessage(sender, message, mentions, linkPreview, quoted, null, firstBatch); + // firstMessage = await this.sendMessage( + // sender, + // message, + // mentions, + // linkPreview, + // quoted, + // null, + // group?.ephemeralDuration, + // firstBatch, + // ); // msgId = firstMessage.key.id; // } @@ -1994,12 +1884,23 @@ export class BaileysStartupService extends ChannelStartupService { // await Promise.allSettled( // batches.map(async (batch: GroupParticipant[]) => { - // const messageSent = await this.sendMessage(sender, message, mentions, linkPreview, quoted, msgId, batch); + // const messageSent = await this.sendMessage( + // sender, + // message, + // mentions, + // linkPreview, + // quoted, + // msgId, + // group?.ephemeralDuration, + // batch, + // ); // return messageSent; // }), // ); + // messageSent = firstMessage; + messageSent = await this.sendMessage( sender, message, @@ -2008,6 +1909,7 @@ export class BaileysStartupService extends ChannelStartupService { quoted, null, group?.ephemeralDuration, + group.participants, ); } else { messageSent = await this.sendMessage(sender, message, mentions, linkPreview, quoted); diff --git a/src/utils/use-multi-file-auth-state-prisma.ts b/src/utils/use-multi-file-auth-state-prisma.ts index c119bc5e..b8af060b 100644 --- a/src/utils/use-multi-file-auth-state-prisma.ts +++ b/src/utils/use-multi-file-auth-state-prisma.ts @@ -2,6 +2,7 @@ import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from import fs from 'fs/promises'; import path from 'path'; +import { CacheService } from '../api/services/cache.service'; import { INSTANCE_DIR } from '../config/path.config'; import { prismaServer } from '../libs/prisma.connect'; @@ -77,7 +78,10 @@ async function fileExists(file: string): Promise { } } -export default async function useMultiFileAuthStatePrisma(sessionId: string): Promise<{ +export default async function useMultiFileAuthStatePrisma( + sessionId: string, + cache: CacheService, +): Promise<{ state: AuthenticationState; saveCreds: () => Promise; }> { @@ -89,8 +93,9 @@ export default async function useMultiFileAuthStatePrisma(sessionId: string): Pr const dataString = JSON.stringify(data, BufferJSON.replacer); if (key != 'creds') { - await fs.writeFile(localFile(key), dataString); - return; + return await cache.hSet(sessionId, key, data); + // await fs.writeFile(localFile(key), dataString); + // return; } await saveKey(sessionId, dataString); return; @@ -101,8 +106,9 @@ export default async function useMultiFileAuthStatePrisma(sessionId: string): Pr let rawData; if (key != 'creds') { - if (!(await fileExists(localFile(key)))) return null; - rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' }); + return await cache.hGet(sessionId, key); + // if (!(await fileExists(localFile(key)))) return null; + // rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' }); } else { rawData = await getAuthKey(sessionId); } @@ -117,7 +123,8 @@ export default async function useMultiFileAuthStatePrisma(sessionId: string): Pr async function removeData(key: string): Promise { try { if (key != 'creds') { - await fs.unlink(localFile(key)); + return await cache.hDelete(sessionId, key); + // await fs.unlink(localFile(key)); } else { await deleteAuthKey(sessionId); }