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); }