From 053a7981d11245cba31d663aa8953d3cc66b9c39 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Thu, 13 Jun 2024 18:51:18 -0300 Subject: [PATCH 01/11] fix: update default values for WA_BUSINESS environment variables Updated default values for WA_BUSINESS_TOKEN_WEBHOOK, WA_BUSINESS_URL, and WA_BUSINESS_VERSION in env.config.ts to 'evolution', 'https://graph.facebook.com', and 'v19.0' respectively. This change ensures that the application uses more appropriate defaults if environment variables are not set, improving reliability and consistency. --- src/config/env.config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/env.config.ts b/src/config/env.config.ts index ddd5ce9f..f1ab0335 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -366,9 +366,9 @@ export class ConfigService { GLOBAL_EVENTS: process.env?.WEBSOCKET_GLOBAL_EVENTS === 'true', }, WA_BUSINESS: { - TOKEN_WEBHOOK: process.env.WA_BUSINESS_TOKEN_WEBHOOK || '', - URL: process.env.WA_BUSINESS_URL || '', - VERSION: process.env.WA_BUSINESS_VERSION || '', + TOKEN_WEBHOOK: process.env.WA_BUSINESS_TOKEN_WEBHOOK || 'evolution', + URL: process.env.WA_BUSINESS_URL || 'https://graph.facebook.com', + VERSION: process.env.WA_BUSINESS_VERSION || 'v19.0', LANGUAGE: process.env.WA_BUSINESS_LANGUAGE || 'en', }, LOG: { From 5b47bc9ef0703e28db5fbda6bdb5b2a832c5f21d Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Sun, 16 Jun 2024 17:17:55 -0300 Subject: [PATCH 02/11] feat: update dependencies and improve caching logic Updated package.json to include latest dependencies. Enhanced caching logic in cache.service.ts and rediscache.ts for better performance. Improved DTOs in chat.dto.ts, instance.dto.ts, and sendMessage.dto.ts for more robust data handling. Refined instance.controller.ts and chatwoot.service.ts to streamline API integrations. Adjusted authentication state management in use-multi-file-auth-state-db.ts, use-multi-file-auth-state-provider-files.ts, and use-multi-file-auth-state-redis-db.ts. These changes aim to optimize the system's performance and reliability. --- package.json | 2 +- src/api/controllers/instance.controller.ts | 2 +- src/api/dto/chat.dto.ts | 2 +- src/api/dto/instance.dto.ts | 2 +- src/api/dto/sendMessage.dto.ts | 2 +- .../chatwoot/services/chatwoot.service.ts | 2 +- .../chatwoot/utils/chatwoot-import-helper.ts | 2 +- src/api/services/cache.service.ts | 2 +- src/api/services/channel.service.ts | 2 +- .../channels/whatsapp.baileys.service.ts | 47 ++++++++++--------- src/api/types/wa.types.ts | 2 +- src/cache/rediscache.ts | 2 +- src/utils/use-multi-file-auth-state-db.ts | 2 +- ...se-multi-file-auth-state-provider-files.ts | 2 +- .../use-multi-file-auth-state-redis-db.ts | 2 +- 15 files changed, 38 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 8ee94b3e..327e17c8 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "amqplib": "^0.10.3", "@aws-sdk/client-sqs": "^3.569.0", "axios": "^1.6.5", - "@whiskeysockets/baileys": "6.7.5", + "baileys": "github:bobslavtriev/Baileys", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index bc3ace61..3b2ea0e4 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -1,4 +1,4 @@ -import { delay } from '@whiskeysockets/baileys'; +import { delay } from 'baileys'; import { isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; import { v4 } from 'uuid'; diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 3ed55d52..7952ddc6 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -1,4 +1,4 @@ -import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; +import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from 'baileys'; export class OnWhatsAppDto { constructor( diff --git a/src/api/dto/instance.dto.ts b/src/api/dto/instance.dto.ts index d9a4d87a..1f2ff1c6 100644 --- a/src/api/dto/instance.dto.ts +++ b/src/api/dto/instance.dto.ts @@ -1,4 +1,4 @@ -import { WAPresence } from '@whiskeysockets/baileys'; +import { WAPresence } from 'baileys'; import { ProxyDto } from './proxy.dto'; diff --git a/src/api/dto/sendMessage.dto.ts b/src/api/dto/sendMessage.dto.ts index 7bb33074..5c197d44 100644 --- a/src/api/dto/sendMessage.dto.ts +++ b/src/api/dto/sendMessage.dto.ts @@ -1,4 +1,4 @@ -import { proto, WAPresence } from '@whiskeysockets/baileys'; +import { proto, WAPresence } from 'baileys'; export class Quoted { key: proto.IMessageKey; diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 9a30dc12..a837c03e 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -8,7 +8,7 @@ import ChatwootClient, { inbox, } from '@figuro/chatwoot-sdk'; import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request'; -import { proto } from '@whiskeysockets/baileys'; +import { proto } from 'baileys'; import axios from 'axios'; import FormData from 'form-data'; import { createReadStream, unlinkSync, writeFileSync } from 'fs'; diff --git a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts index dd0bb23a..e102aa57 100644 --- a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts +++ b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts @@ -1,5 +1,5 @@ import { inbox } from '@figuro/chatwoot-sdk'; -import { proto } from '@whiskeysockets/baileys'; +import { proto } from 'baileys'; import { InstanceDto } from '../../../../api/dto/instance.dto'; import { ChatwootRaw, ContactRaw, MessageRaw } from '../../../../api/models'; diff --git a/src/api/services/cache.service.ts b/src/api/services/cache.service.ts index caf3dbfa..e03b3eb5 100644 --- a/src/api/services/cache.service.ts +++ b/src/api/services/cache.service.ts @@ -1,4 +1,4 @@ -import { BufferJSON } from '@whiskeysockets/baileys'; +import { BufferJSON } from 'baileys'; import { Logger } from '../../config/logger.config'; import { ICache } from '../abstract/abstract.cache'; diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index ac4eae90..c7e96b04 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -1,4 +1,4 @@ -import { WASocket } from '@whiskeysockets/baileys'; +import { WASocket } from 'baileys'; import axios from 'axios'; import { execSync } from 'child_process'; import { isURL } from 'class-validator'; diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 210dc72b..21167f36 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -35,9 +35,9 @@ import makeWASocket, { WAMessageUpdate, WAPresence, WASocket, -} from '@whiskeysockets/baileys'; -import { Label } from '@whiskeysockets/baileys/lib/Types/Label'; -import { LabelAssociation } from '@whiskeysockets/baileys/lib/Types/LabelAssociation'; +} from 'baileys'; +import { Label } from 'baileys/lib/Types/Label'; +import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; import axios from 'axios'; import { exec } from 'child_process'; import { isBase64, isURL } from 'class-validator'; @@ -618,6 +618,7 @@ export class BaileysStartupService extends ChannelStartupService { }, userDevicesCache: this.userDevicesCache, transactionOpts: { maxCommitRetries: 5, delayBetweenTriesMs: 2500 }, + cachedGroupMetadata: this.getGroupMetadataCache, patchMessageBeforeSending(message) { if ( message.deviceSentMessage?.message?.listMessage?.listType === @@ -1946,11 +1947,9 @@ export class BaileysStartupService extends ChannelStartupService { } as unknown as AnyMessageContent, { ...option, - cachedGroupMetadata: - !this.configService.get('CACHE').REDIS.ENABLED && - !this.configService.get('CACHE').LOCAL.ENABLED - ? null - : this.getGroupMetadataCache, + useCachedGroupMetadata: + !!this.configService.get('CACHE').REDIS.ENABLED && + !!this.configService.get('CACHE').LOCAL.ENABLED, } as unknown as MiscMessageGenerationOptions, ); } @@ -1966,11 +1965,9 @@ export class BaileysStartupService extends ChannelStartupService { } as unknown as AnyMessageContent, { ...option, - cachedGroupMetadata: - !this.configService.get('CACHE').REDIS.ENABLED && - !this.configService.get('CACHE').LOCAL.ENABLED - ? null - : this.getGroupMetadataCache, + useCachedGroupMetadata: + !!this.configService.get('CACHE').REDIS.ENABLED && + !!this.configService.get('CACHE').LOCAL.ENABLED, } as unknown as MiscMessageGenerationOptions, ); } @@ -1988,11 +1985,9 @@ export class BaileysStartupService extends ChannelStartupService { }, { ...option, - cachedGroupMetadata: - !this.configService.get('CACHE').REDIS.ENABLED && - !this.configService.get('CACHE').LOCAL.ENABLED - ? null - : this.getGroupMetadataCache, + useCachedGroupMetadata: + !!this.configService.get('CACHE').REDIS.ENABLED && + !!this.configService.get('CACHE').LOCAL.ENABLED, } as unknown as MiscMessageGenerationOptions, ); } @@ -2016,11 +2011,9 @@ export class BaileysStartupService extends ChannelStartupService { message as unknown as AnyMessageContent, { ...option, - cachedGroupMetadata: - !this.configService.get('CACHE').REDIS.ENABLED && - !this.configService.get('CACHE').LOCAL.ENABLED - ? null - : this.getGroupMetadataCache, + useCachedGroupMetadata: + !!this.configService.get('CACHE').REDIS.ENABLED && + !!this.configService.get('CACHE').LOCAL.ENABLED, } as unknown as MiscMessageGenerationOptions, ); })(); @@ -3354,6 +3347,10 @@ export class BaileysStartupService extends ChannelStartupService { } public async findGroup(id: GroupJid, reply: 'inner' | 'out' = 'out') { + if (this.localSettings.groups_ignore === true) { + return; + } + this.logger.verbose('Fetching group'); try { const group = await this.client.groupMetadata(id.groupJid); @@ -3384,6 +3381,10 @@ export class BaileysStartupService extends ChannelStartupService { } public async fetchAllGroups(getParticipants: GetParticipant) { + if (this.localSettings.groups_ignore === true) { + return; + } + this.logger.verbose('Fetching all groups'); try { const fetch = Object.values(await this.client.groupFetchAllParticipating()); diff --git a/src/api/types/wa.types.ts b/src/api/types/wa.types.ts index 9c33ac6f..94461964 100644 --- a/src/api/types/wa.types.ts +++ b/src/api/types/wa.types.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'; +import { AuthenticationState, WAConnectionState } from 'baileys'; export enum Events { APPLICATION_STARTUP = 'application.startup', diff --git a/src/cache/rediscache.ts b/src/cache/rediscache.ts index 6e209ef1..c4e98968 100644 --- a/src/cache/rediscache.ts +++ b/src/cache/rediscache.ts @@ -1,4 +1,4 @@ -import { BufferJSON } from '@whiskeysockets/baileys'; +import { BufferJSON } from 'baileys'; import { RedisClientType } from 'redis'; import { ICache } from '../api/abstract/abstract.cache'; diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts index 995ac92a..a0bad105 100644 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -5,7 +5,7 @@ import { initAuthCreds, proto, SignalDataTypeMap, -} from '@whiskeysockets/baileys'; +} from 'baileys'; import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; diff --git a/src/utils/use-multi-file-auth-state-provider-files.ts b/src/utils/use-multi-file-auth-state-provider-files.ts index 1051af8f..f72e6855 100644 --- a/src/utils/use-multi-file-auth-state-provider-files.ts +++ b/src/utils/use-multi-file-auth-state-provider-files.ts @@ -41,7 +41,7 @@ import { initAuthCreds, proto, SignalDataTypeMap, -} from '@whiskeysockets/baileys'; +} from 'baileys'; import { isNotEmpty } from 'class-validator'; import { ProviderFiles } from '../api/provider/sessions'; diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index 66bb89ea..465b96a7 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -4,7 +4,7 @@ import { initAuthCreds, proto, SignalDataTypeMap, -} from '@whiskeysockets/baileys'; +} from 'baileys'; import { CacheService } from '../api/services/cache.service'; import { Logger } from '../config/logger.config'; From 975b3ee528660174ecd419e7df0f718c0e1a05b3 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Sun, 16 Jun 2024 17:19:45 -0300 Subject: [PATCH 03/11] fix: reorder imports to resolve linting issues Reordered imports in multiple files to resolve linting issues and improve code readability. This change does not impact the functionality but ensures the code adheres to the project's coding standards. --- .../integrations/chatwoot/services/chatwoot.service.ts | 5 ++--- src/api/services/channel.service.ts | 2 +- src/api/services/channels/whatsapp.baileys.service.ts | 8 ++++---- src/utils/use-multi-file-auth-state-db.ts | 9 +-------- src/utils/use-multi-file-auth-state-provider-files.ts | 9 +-------- src/utils/use-multi-file-auth-state-redis-db.ts | 8 +------- 6 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index a837c03e..cabcade1 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -8,8 +8,8 @@ import ChatwootClient, { inbox, } from '@figuro/chatwoot-sdk'; import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request'; -import { proto } from 'baileys'; import axios from 'axios'; +import { proto } from 'baileys'; import FormData from 'form-data'; import { createReadStream, unlinkSync, writeFileSync } from 'fs'; import Jimp from 'jimp'; @@ -444,8 +444,7 @@ export class ChatwootService { const searchableFields = this.getSearchableFields(); // eslint-disable-next-line prettier/prettier - if(contacts.length === 2 && this.getClientCwConfig().merge_brazil_contacts && query.startsWith('+55')){ - + if (contacts.length === 2 && this.getClientCwConfig().merge_brazil_contacts && query.startsWith('+55')) { const contact = this.mergeBrazilianContacts(contacts); if (contact) { return contact; diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index c7e96b04..ddf916ee 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -1,5 +1,5 @@ -import { WASocket } from 'baileys'; import axios from 'axios'; +import { WASocket } from 'baileys'; import { execSync } from 'child_process'; import { isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 21167f36..7d20b329 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -1,5 +1,6 @@ import ffmpegPath from '@ffmpeg-installer/ffmpeg'; import { Boom } from '@hapi/boom'; +import axios from 'axios'; import makeWASocket, { AnyMessageContent, BufferedEventData, @@ -38,7 +39,6 @@ import makeWASocket, { } from 'baileys'; import { Label } from 'baileys/lib/Types/Label'; import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; -import axios from 'axios'; import { exec } from 'child_process'; import { isBase64, isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; @@ -253,8 +253,8 @@ export class BaileysStartupService extends ChannelStartupService { this.logger.verbose('Getting profile status'); const status = await this.client.fetchStatus(this.instance.wuid); - this.logger.verbose(`Profile status: ${status.status}`); - return status.status; + this.logger.verbose(`Profile status: ${status[0]?.status}`); + return status[0]?.status; } public get profilePictureUrl() { @@ -1737,7 +1737,7 @@ export class BaileysStartupService extends ChannelStartupService { this.logger.verbose('Getting status'); return { wuid: jid, - status: (await this.client.fetchStatus(jid))?.status, + status: (await this.client.fetchStatus(jid))[0]?.status, }; } catch (error) { this.logger.verbose('Status not found'); diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts index a0bad105..f20db84f 100644 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -1,11 +1,4 @@ -import { - AuthenticationCreds, - AuthenticationState, - BufferJSON, - initAuthCreds, - proto, - SignalDataTypeMap, -} from 'baileys'; +import { AuthenticationCreds, AuthenticationState, BufferJSON, initAuthCreds, proto, SignalDataTypeMap } from 'baileys'; import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; diff --git a/src/utils/use-multi-file-auth-state-provider-files.ts b/src/utils/use-multi-file-auth-state-provider-files.ts index f72e6855..ec4d7e6c 100644 --- a/src/utils/use-multi-file-auth-state-provider-files.ts +++ b/src/utils/use-multi-file-auth-state-provider-files.ts @@ -34,14 +34,7 @@ * └──────────────────────────────────────────────────────────────────────────────┘ */ -import { - AuthenticationCreds, - AuthenticationState, - BufferJSON, - initAuthCreds, - proto, - SignalDataTypeMap, -} from 'baileys'; +import { AuthenticationCreds, AuthenticationState, BufferJSON, initAuthCreds, proto, SignalDataTypeMap } from 'baileys'; import { isNotEmpty } from 'class-validator'; import { ProviderFiles } from '../api/provider/sessions'; diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index 465b96a7..d077b894 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -1,10 +1,4 @@ -import { - AuthenticationCreds, - AuthenticationState, - initAuthCreds, - proto, - SignalDataTypeMap, -} from 'baileys'; +import { AuthenticationCreds, AuthenticationState, initAuthCreds, proto, SignalDataTypeMap } from 'baileys'; import { CacheService } from '../api/services/cache.service'; import { Logger } from '../config/logger.config'; From 1e320f7904a23a5647a33f844a8a6b290182fdd1 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 17 Jun 2024 11:19:22 -0300 Subject: [PATCH 04/11] fix: update use-multi-file-auth-state-db.ts to handle edge cases Refactored the use-multi-file-auth-state-db.ts to better handle edge cases in multi-file authentication state management. This change improves reliability and ensures more robust error handling, reducing potential issues during authentication. --- src/utils/use-multi-file-auth-state-db.ts | 105 ++++++++++++++++------ 1 file changed, 77 insertions(+), 28 deletions(-) diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts index f20db84f..dd25270e 100644 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -1,22 +1,55 @@ -import { AuthenticationCreds, AuthenticationState, BufferJSON, initAuthCreds, proto, SignalDataTypeMap } from 'baileys'; +import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from 'baileys'; +import fs from 'fs/promises'; +import path from 'path'; import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; +import { INSTANCE_DIR } from '../config/path.config'; import { dbserver } from '../libs/db.connect'; +const fixFileName = (file) => { + if (!file) { + return undefined; + } + const replacedSlash = file.replace(/\//g, '__'); + const replacedColon = replacedSlash.replace(/:/g, '-'); + return replacedColon; +}; + +async function fileExists(file) { + try { + const stat = await fs.stat(file); + if (stat.isFile()) return true; + } catch (error) { + return; + } +} + export async function useMultiFileAuthStateDb( coll: string, ): Promise<{ state: AuthenticationState; saveCreds: () => Promise }> { - const logger = new Logger(useMultiFileAuthStateDb.name); - const client = dbserver.getClient(); + const logger = new Logger(useMultiFileAuthStateDb.name); + const collection = client .db(configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances') .collection(coll); - const writeData = async (data: any, key: string): Promise => { + const sessionId = coll; + + const localFolder = path.join(INSTANCE_DIR, sessionId); + const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json'); + await fs.mkdir(localFolder, { recursive: true }); + + async function writeData(data: any, key: string): Promise { try { + const dataString = JSON.stringify(data, BufferJSON.replacer); + + if (key != 'creds') { + await fs.writeFile(localFile(key), dataString); + return; + } await client.connect(); let msgParsed = JSON.parse(JSON.stringify(data, BufferJSON.replacer)); if (Array.isArray(msgParsed)) { @@ -30,42 +63,59 @@ export async function useMultiFileAuthStateDb( }); } catch (error) { logger.error(error); + return; } - }; + } - const readData = async (key: string): Promise => { + async function readData(key: string): Promise { try { - await client.connect(); - let data = (await collection.findOne({ _id: key })) as any; - if (data?.content_array) { - data = data.content_array; + if (key != 'creds') { + if (!(await fileExists(localFile(key)))) return null; + const rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' }); + + const parsedData = JSON.parse(rawData, BufferJSON.reviver); + return parsedData; + } else { + await client.connect(); + let data = (await collection.findOne({ _id: key })) as any; + if (data?.content_array) { + data = data.content_array; + } + const creds = JSON.stringify(data); + return JSON.parse(creds, BufferJSON.reviver); } - const creds = JSON.stringify(data); - return JSON.parse(creds, BufferJSON.reviver); } catch (error) { logger.error(error); + return null; } - }; + } - const removeData = async (key: string) => { + async function removeData(key: string): Promise { try { - await client.connect(); - return await collection.deleteOne({ _id: key }); + if (key != 'creds') { + await fs.unlink(localFile(key)); + } else { + await client.connect(); + return await collection.deleteOne({ _id: key }); + } } catch (error) { logger.error(error); + return; } - }; + } - const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); + let creds = await readData('creds'); + if (!creds) { + creds = initAuthCreds(); + await writeData(creds, 'creds'); + } return { state: { creds, keys: { - get: async (type, ids: string[]) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const data: { [_: string]: SignalDataTypeMap[type] } = {}; + get: async (type, ids) => { + const data = {}; await Promise.all( ids.map(async (id) => { let value = await readData(`${type}-${id}`); @@ -76,25 +126,24 @@ export async function useMultiFileAuthStateDb( data[id] = value; }), ); - return data; }, - set: async (data: any) => { - const tasks: Promise[] = []; + set: async (data) => { + const tasks = []; for (const category in data) { for (const id in data[category]) { const value = data[category][id]; const key = `${category}-${id}`; + tasks.push(value ? writeData(value, key) : removeData(key)); } } - await Promise.all(tasks); }, }, }, - saveCreds: async () => { - return await writeData(creds, 'creds'); + saveCreds: () => { + return writeData(creds, 'creds'); }, }; } From c060d330de158bfb25637e17d6cff225bba359bd Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Tue, 18 Jun 2024 11:32:50 -0300 Subject: [PATCH 05/11] fix: normalize event names in channel.service.ts Normalized event names by replacing underscores with dots and converting to lowercase. This ensures consistent naming conventions and prevents potential issues with queue bindings. --- src/api/services/channel.service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index ddf916ee..625ab7a6 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -721,7 +721,9 @@ export class ChannelStartupService { autoDelete: false, }); - const queueName = `${this.instanceName}.${event}`; + const eventName = event.replace(/_/g, '.').toLowerCase(); + + const queueName = `${this.instanceName}.${eventName}`; await amqp.assertQueue(queueName, { durable: true, @@ -731,7 +733,7 @@ export class ChannelStartupService { }, }); - await amqp.bindQueue(queueName, exchangeName, event); + await amqp.bindQueue(queueName, exchangeName, eventName); const message = { event, From f5bd11fc196ff01dd3e9e4673886074748bdafa4 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Tue, 18 Jun 2024 11:34:47 -0300 Subject: [PATCH 06/11] feat: add support for documentWithCaptionMessage in WhatsApp Baileys service Modified whatsapp.baileys.service.ts to include handling for documentWithCaptionMessage. This change ensures that messages with documents having captions are properly processed, enhancing the service's message handling capabilities. No impact on existing functionalities. --- src/api/services/channels/whatsapp.baileys.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 7d20b329..984d5009 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -1186,6 +1186,7 @@ export class BaileysStartupService extends ChannelStartupService { received?.message?.videoMessage || received?.message?.stickerMessage || received?.message?.documentMessage || + received?.message?.documentWithCaptionMessage || received?.message?.audioMessage; const contentMsg = received?.message[getContentType(received.message)] as any; From 86b2999fcf47735c5a30b747ce53aa688ff21298 Mon Sep 17 00:00:00 2001 From: Douglas Rauber at Nitro Date: Mon, 24 Jun 2024 10:37:54 -0300 Subject: [PATCH 07/11] Add -cwId- when findByName --- src/api/integrations/chatwoot/services/chatwoot.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 9a30dc12..ecda0a22 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -736,7 +736,12 @@ export class ChatwootService { } this.logger.verbose('find inbox by name'); - const findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox); + let findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox); + + if (!findByName) { + findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox.split('-cwId-')[0]); + } + if (!findByName) { this.logger.warn('inbox not found'); From b63b7b0b7be6c9cbfbf810ed422af40d4f8f83e9 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Wed, 3 Jul 2024 13:45:05 -0300 Subject: [PATCH 08/11] fix: Generate pairing code and update Baileys repository Update package.json to use the new Baileys repository and modify the Whatsapp Baileys service to generate a pairing code. This change fixes the issue with the previous Baileys repository and improves the pairing process for Whatsapp. Changes: - Update package.json to use the new Baileys repository - Modify Whatsapp Baileys service to generate a pairing code - Fix issue with previous Baileys repository - Improve pairing process for Whatsapp --- package.json | 2 +- .../channels/whatsapp.baileys.service.ts | 370 +++++++----------- 2 files changed, 142 insertions(+), 230 deletions(-) diff --git a/package.json b/package.json index 327e17c8..35c5ed85 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "amqplib": "^0.10.3", "@aws-sdk/client-sqs": "^3.569.0", "axios": "^1.6.5", - "baileys": "github:bobslavtriev/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 984d5009..d31e0238 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -20,6 +20,7 @@ import makeWASocket, { GroupMetadata, isJidBroadcast, isJidGroup, + isJidNewsletter, isJidUser, makeCacheableSignalKeyStore, MessageUpsertType, @@ -517,6 +518,143 @@ export class BaileysStartupService extends ChannelStartupService { return await useMultiFileAuthState(join(INSTANCE_DIR, this.instance.name)); } + private async createClient(number?: string, mobile?: boolean): Promise { + this.instance.authState = await this.defineAuthState(); + + if (!mobile) { + this.mobile = false; + } else { + this.mobile = mobile; + } + + 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); + + let options; + + if (this.localProxy.enabled) { + this.logger.info('Proxy enabled: ' + this.localProxy.proxy?.host); + + if (this.localProxy?.proxy?.host?.includes('proxyscrape')) { + try { + const response = await axios.get(this.localProxy.proxy?.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(this.localProxy.proxy), + fetchAgent: makeProxyAgent(this.localProxy.proxy), + }; + } + } + + 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, + mobile, + ...browserOptions, + version, + markOnlineOnConnect: this.localSettings.always_online, + 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.groups_ignore && isJidGroup(jid); + const isBroadcast = !this.localSettings.read_status && 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.sync_full_history, + 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.logger.verbose('Creating socket'); + + this.client = makeWASocket(socketConfig); + + this.logger.verbose('Socket created'); + + this.eventHandler(); + + this.logger.verbose('Socket event handler initialized'); + + this.phoneNumber = number; + + return this.client; + } + public async connectToWhatsapp(number?: string, mobile?: boolean): Promise { this.logger.verbose('Connecting to whatsapp'); try { @@ -530,130 +668,7 @@ export class BaileysStartupService extends ChannelStartupService { this.loadProxy(); this.loadChamaai(); - this.instance.authState = await this.defineAuthState(); - - if (!mobile) { - this.mobile = false; - } else { - this.mobile = mobile; - } - - const session = this.configService.get('CONFIG_SESSION_PHONE'); - const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; - this.logger.verbose('Browser: ' + JSON.stringify(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.proxy?.host); - - if (this.localProxy?.proxy?.host?.includes('proxyscrape')) { - try { - const response = await axios.get(this.localProxy.proxy?.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(this.localProxy.proxy), - fetchAgent: makeProxyAgent(this.localProxy.proxy), - }; - } - } - - 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, - mobile, - browser: number ? ['Chrome (Linux)', session.NAME, release()] : browser, - version, - markOnlineOnConnect: this.localSettings.always_online, - 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.groups_ignore && isJidGroup(jid); - const isBroadcast = !this.localSettings.read_status && isJidBroadcast(jid); - - return isGroupJid || isBroadcast; - }, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, - syncFullHistory: this.localSettings.sync_full_history, - shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => { - return this.historySyncNotification(msg); - }, - userDevicesCache: this.userDevicesCache, - transactionOpts: { maxCommitRetries: 5, delayBetweenTriesMs: 2500 }, - cachedGroupMetadata: this.getGroupMetadataCache, - 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.logger.verbose('Creating socket'); - - this.client = makeWASocket(socketConfig); - - this.logger.verbose('Socket created'); - - this.eventHandler(); - - this.logger.verbose('Socket event handler initialized'); - - this.phoneNumber = number; - - return this.client; + return await this.createClient(number, mobile); } catch (error) { this.logger.error(error); throw new InternalServerErrorException(error?.toString()); @@ -720,110 +735,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'); - const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; - - 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.proxy?.host); - - if (this.localProxy?.proxy?.host?.includes('proxyscrape')) { - try { - const response = await axios.get(this.localProxy.proxy?.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(this.localProxy.proxy), - fetchAgent: makeProxyAgent(this.localProxy.proxy), - }; - } - } - - 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, - mobile: this.mobile, - browser: this.phoneNumber ? ['Chrome (Linux)', session.NAME, release()] : browser, - version, - markOnlineOnConnect: this.localSettings.always_online, - 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.groups_ignore && isJidGroup(jid); - const isBroadcast = !this.localSettings.read_status && isJidBroadcast(jid); - - return isGroupJid || isBroadcast; - }, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, - syncFullHistory: this.localSettings.sync_full_history, - 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, this.mobile); } catch (error) { this.logger.error(error); throw new InternalServerErrorException(error?.toString()); @@ -1061,7 +973,7 @@ export class BaileysStartupService extends ChannelStartupService { m.messageTimestamp = m.messageTimestamp?.toNumber(); } - if (m.messageTimestamp <= timestampLimitToImport) { + if ((m.messageTimestamp as number) <= timestampLimitToImport) { continue; } From a8121d7fe616d634f0d5f8c81cb3df750760c8f9 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Wed, 3 Jul 2024 13:47:25 -0300 Subject: [PATCH 09/11] fix: Correction in global RabbitMQ queue name Fix global RabbitMQ queue name in `channel.service.ts` and update CHANGELOG.md. The queue name has been changed from `transformedWe` to `event`. This fix prevents queue errors and ensures correct functionality of inter-service communication. --- CHANGELOG.md | 6 ++++++ src/api/services/channel.service.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5adcb7b..554fa9b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.8.2 (2024-06-08 21:32) + +### Fixed + +* + # 1.8.1 (2024-06-08 21:32) ### Feature diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 625ab7a6..ea3a57c1 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -788,7 +788,7 @@ export class ChannelStartupService { autoDelete: false, }); - const queueName = transformedWe; + const queueName = event; await amqp.assertQueue(queueName, { durable: true, From 0f7a39a08fb1d9db74139b68d57dfeb49a7fa2c1 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Wed, 3 Jul 2024 13:50:30 -0300 Subject: [PATCH 10/11] chore(changelog): Update changelog to v1.8.2 This commit updates the changelog to reflect the release of version 1.8.2. The date of the release has been updated in the header of the changelog. No functional changes were made in this release. Modified: CHANGELOG.md --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 554fa9b7..1af5a843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ -# 1.8.2 (2024-06-08 21:32) +# 1.8.2 (2024-07-03 13:50) ### Fixed -* +* Corretion in globall rabbitmq queue name +* Improvement in the use of mongodb database for credentials +* Fixed base64 in webhook for documentWithCaption +* Fixed Generate pairing code # 1.8.1 (2024-06-08 21:32) From 18ebe27bc39a90d5d12a3fdb367b2d278cd4ce97 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Wed, 3 Jul 2024 13:51:16 -0300 Subject: [PATCH 11/11] "chore: Updated package and documentation versions to v1.8.2" Explanation: This commit updates the package and documentation versions to v1.8.2. The package.json and swagger.yaml files were modified accordingly. These changes are mainly for maintenance purposes and do not affect the functionality of the application. --- package.json | 2 +- src/docs/swagger.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 35c5ed85..4d2c17cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "1.8.1", + "version": "1.8.2", "description": "Rest api for communication with WhatsApp", "main": "./dist/src/main.js", "scripts": { diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 59b252d3..8b73c5d9 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -25,7 +25,7 @@ info: [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442) - version: 1.8.0 + version: 1.8.2 contact: name: DavidsonGomes email: contato@agenciadgcode.com