-
Notifications
You must be signed in to change notification settings - Fork 114
Feature/in 731 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/in 731 #11
Conversation
…ename no envio de mídia document em WABA
Corrige parse de quoted e adiciona retorno de ID após upload de mídia
logs para identificar o valor de contacts que está vindo da baileys
fix: substitui contatos com remoteJid @lid por senderPn
Remove download midia meta
Alteração na coluna token para o tipo text na tabela instances
…ion' agora serão enviados o mediaType como ciphertext
Reviewer's GuideThis PR refactors the Baileys WhatsApp integration to support dynamic webVersion loading, buffer-based media handling, quoted messages parsing, and streamlined webhook history payloads; extends the Business service with location and sticker support; adds new API endpoints for instance management and media downloads; upgrades infrastructure (Docker base image, dependencies, Prisma schema); and deprecates many direct DB write paths in favor of webhook events and logs. Sequence diagram for downloadMediaMessage API flowsequenceDiagram
participant Client
participant BaileysRouter
participant BaileysController
participant BaileysStartupService
Client->>BaileysRouter: POST /downloadMediaMessage
BaileysRouter->>BaileysController: downloadMediaMessage(instance, body)
BaileysController->>BaileysStartupService: baileysDownloadMediaMessage(body)
BaileysStartupService-->>BaileysController: base64 media
BaileysController-->>BaileysRouter: base64 media
BaileysRouter-->>Client: base64 media
Sequence diagram for new instanceByName and countInstances endpointssequenceDiagram
participant Client
participant InstanceRouter
participant InstanceController
participant PrismaRepository
Client->>InstanceRouter: GET /instance/instanceByName
InstanceRouter->>InstanceController: getInstanceByName(instance)
InstanceController->>PrismaRepository: findFirst({ name: instanceName })
PrismaRepository-->>InstanceController: instance data
InstanceController-->>InstanceRouter: response
InstanceRouter-->>Client: response
Client->>InstanceRouter: GET /instance/countInstances
InstanceRouter->>InstanceController: countInstances()
InstanceController->>PrismaRepository: count()
PrismaRepository-->>InstanceController: count
InstanceController-->>InstanceRouter: count
InstanceRouter-->>Client: count
Class diagram for updated BaileysStartupService and related typesclassDiagram
class BaileysStartupService {
+parseQuoted(quoted: Quoted)
+isMedia(message: proto.IMessage): boolean
+baileysDownloadMediaMessage(message: proto.IWebMessageInfo): Promise<string>
+normalizeLidKey(key: proto.IMessageKey): string | undefined
-userDevicesCache: CacheStore
-msgRetryCounterCache: CacheStore
-prepareMessage(message: proto.IWebMessageInfo): any
}
class IMessageKeyWithExtras {
senderPn: string | null
senderLid: string | null
}
class wa {
<<namespace>>
HistorySetData
HistorySetContact
}
BaileysStartupService ..> IMessageKeyWithExtras : uses
BaileysStartupService ..> wa : uses
class wa~HistorySetData~ {
contacts: HistorySetContact[]
messages: any[]
}
class wa~HistorySetContact~ {
id: string
name: string
}
Class diagram for BusinessStartupService message handling changesclassDiagram
class BusinessStartupService {
-messageLocationJson(received: any)
-messageTextJson(received: any)
+isMediaMessage(message: any)
+prepareMediaMessage(mediaMessage: MediaMessage)
+mediaMessage(data: SendMediaDto, file?: any)
+audioWhatsapp(data: SendAudioDto, file?: any)
}
BusinessStartupService ..> SendMediaDto
BusinessStartupService ..> SendAudioDto
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @jhonsw1 - I've reviewed your changes - here's some feedback:
- There’s a lot of commented-out legacy code throughout this PR—please remove dead code or extract it behind feature flags to keep the codebase clean and maintainable.
- The diff uses both console.log and various logger methods for debugging—standardize on your application logger with appropriate levels and remove ad-hoc debug prints.
- This class is extremely large and mixes message handling, event routing, media processing, and caching—consider refactoring into smaller services or modules to improve readability and testability.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- There’s a lot of commented-out legacy code throughout this PR—please remove dead code or extract it behind feature flags to keep the codebase clean and maintainable.
- The diff uses both console.log and various logger methods for debugging—standardize on your application logger with appropriate levels and remove ad-hoc debug prints.
- This class is extremely large and mixes message handling, event routing, media processing, and caching—consider refactoring into smaller services or modules to improve readability and testability.
## Individual Comments
### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:899` </location>
<code_context>
- }
+ this.logger.info("VALOR DA MENSAGEM: " + JSON.stringify(received))
+
+ received.key.remoteJid = this.normalizeLidKey(received?.key);
if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) {
</code_context>
<issue_to_address>
Overwriting remoteJid may have unintended side effects.
Consider cloning received.key or assigning the normalized value to a new variable to prevent unintended side effects from modifying remoteJid directly.
Suggested implementation:
```typescript
this.logger.info("VALOR DA MENSAGEM: " + JSON.stringify(received))
// Clone received.key to avoid mutating the original object
const normalizedKey = { ...received.key, remoteJid: this.normalizeLidKey(received?.key) };
if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) {
const editedMessage =
}
}
let groupInfo;
const { remoteJid } = normalizedKey;
if (isJidGroup(remoteJid)) {
const groupMetaData = await this.getGroupMetadataCache(remoteJid);
groupInfo = {
```
If there are other usages of `received.key.remoteJid` later in the function, you should update them to use `normalizedKey.remoteJid` instead to ensure consistency and avoid side effects.
</issue_to_address>
### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:2230` </location>
<code_context>
const prepareMedia = await prepareWAMessageMedia(
{
- [type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'),
+ [type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : mediaMessage.media,
} as any,
{ upload: this.client.waUploadToServer },
</code_context>
<issue_to_address>
Directly passing mediaMessage.media may cause issues if not a Buffer.
Validate that mediaMessage.media is either a Buffer or a valid URL before passing it to prepareWAMessageMedia to prevent runtime errors.
</issue_to_address>
### Comment 3
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:2376` </location>
<code_context>
public async mediaMessage(data: SendMediaDto, file?: any) {
const mediaData: SendMediaDto = { ...data };
+ data.quoted = this.parseQuoted(data.quoted);
- if (file) mediaData.media = file.buffer.toString('base64');
</code_context>
<issue_to_address>
Mutating input data object may cause side effects.
Assign the result of parseQuoted to a new variable instead of mutating data directly to avoid unintended side effects.
Suggested implementation:
```typescript
public async mediaMessage(data: SendMediaDto, file?: any) {
const quotedMessage = this.parseQuoted(data.quoted);
const mediaData: SendMediaDto = { ...data, quoted: quotedMessage };
if (file) mediaData.media = file.buffer;
```
```typescript
public async audioWhatsapp(data: SendAudioDto, file?: any) {
const quotedMessage = this.parseQuoted(data.quoted);
const mediaData: SendAudioDto = { ...data, quoted: quotedMessage };
if (file?.buffer) {
```
</issue_to_address>
### Comment 4
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:2432` </location>
<code_context>
if (file?.buffer) {
- mediaData.audio = file.buffer.toString('base64');
+ // mediaData.audio: Buffer = file.buffer;
} else if (!isURL(data.audio) && !isBase64(data.audio)) {
console.error('Invalid file or audio source');
</code_context>
<issue_to_address>
Commented-out code for audio assignment may cause confusion.
Please remove the commented-out assignment or clarify its purpose to avoid confusion during maintenance.
</issue_to_address>
### Comment 5
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:2442` </location>
<code_context>
return await this.sendMessageWithTyping<AnyMessageContent>(
data.number,
{
- audio: isURL(data.audio) ? { url: data.audio } : Buffer.from(data.audio, 'base64'),
+ audio: isURL(data.audio) ? { url: data.audio } : file?.buffer,
ptt: true,
mimetype: 'audio/ogg; codecs=opus',
},
- { presence: 'recording', delay: data?.delay },
+ { presence: 'recording', delay: data?.delay, quoted: data?.quoted },
);
}
</code_context>
<issue_to_address>
Passing quoted in options may break if not handled by sendMessageWithTyping.
Verify that sendMessageWithTyping and related logic correctly process the quoted field, including cases where it is an object.
Suggested implementation:
```typescript
public async audioWhatsapp(data: SendAudioDto, file?: any) {
const mediaData: SendAudioDto = { ...data };
data.quoted = this.parseQuoted(data.quoted);
// Ensure quoted is in the correct format for Baileys
let quotedMessage = undefined;
if (data?.quoted) {
// If quoted is an object, validate/transform as needed
if (typeof data.quoted === 'object' && data.quoted.messageId && data.quoted.remoteJid) {
quotedMessage = {
key: {
id: data.quoted.messageId,
remoteJid: data.quoted.remoteJid,
fromMe: data.quoted.fromMe ?? false,
},
message: data.quoted.message,
};
} else {
quotedMessage = data.quoted;
}
}
if (file?.buffer) {
// mediaData.audio: Buffer = file.buffer;
} else if (!isURL(data.audio) && !isBase64(data.audio)) {
console.error('Invalid file or audio source');
throw new BadRequestException('File buffer, URL, or base64 audio is required');
```
```typescript
return await this.sendMessageWithTyping<AnyMessageContent>(
data.number,
{
audio: isURL(data.audio) ? { url: data.audio } : file?.buffer,
ptt: true,
mimetype: 'audio/ogg; codecs=opus',
},
{ presence: 'recording', delay: data?.delay, quoted: quotedMessage },
);
}
```
You must also verify the implementation of `sendMessageWithTyping`:
- Ensure it passes the `quoted` field to the underlying Baileys `sendMessage` call.
- If not, update `sendMessageWithTyping` to do so, and ensure it expects `quoted` in the correct format (usually as a message object).
- If `parseQuoted` does not guarantee the correct format, consider moving the transformation logic there instead.
</issue_to_address>
### Comment 6
<location> `src/api/integrations/channel/meta/whatsapp.business.service.ts:197` </location>
<code_context>
+ name: message.location.name,
+ },
+ };
+ message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
+ return content;
+ }
</code_context>
<issue_to_address>
Assignment in ternary has no effect if condition is false.
Using an if statement would make the assignment clearer and avoid confusion when message.context is falsy.
</issue_to_address>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
} | ||
this.logger.info("VALOR DA MENSAGEM: " + JSON.stringify(received)) | ||
|
||
received.key.remoteJid = this.normalizeLidKey(received?.key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Overwriting remoteJid may have unintended side effects.
Consider cloning received.key or assigning the normalized value to a new variable to prevent unintended side effects from modifying remoteJid directly.
Suggested implementation:
this.logger.info("VALOR DA MENSAGEM: " + JSON.stringify(received))
// Clone received.key to avoid mutating the original object
const normalizedKey = { ...received.key, remoteJid: this.normalizeLidKey(received?.key) };
if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) {
const editedMessage =
}
}
let groupInfo;
const { remoteJid } = normalizedKey;
if (isJidGroup(remoteJid)) {
const groupMetaData = await this.getGroupMetadataCache(remoteJid);
groupInfo = {
If there are other usages of received.key.remoteJid
later in the function, you should update them to use normalizedKey.remoteJid
instead to ensure consistency and avoid side effects.
@@ -2189,7 +2227,7 @@ export class BaileysStartupService extends ChannelStartupService { | |||
|
|||
const prepareMedia = await prepareWAMessageMedia( | |||
{ | |||
[type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'), | |||
[type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : mediaMessage.media, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Directly passing mediaMessage.media may cause issues if not a Buffer.
Validate that mediaMessage.media is either a Buffer or a valid URL before passing it to prepareWAMessageMedia to prevent runtime errors.
@@ -2335,8 +2373,9 @@ export class BaileysStartupService extends ChannelStartupService { | |||
|
|||
public async mediaMessage(data: SendMediaDto, file?: any) { | |||
const mediaData: SendMediaDto = { ...data }; | |||
data.quoted = this.parseQuoted(data.quoted); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Mutating input data object may cause side effects.
Assign the result of parseQuoted to a new variable instead of mutating data directly to avoid unintended side effects.
Suggested implementation:
public async mediaMessage(data: SendMediaDto, file?: any) {
const quotedMessage = this.parseQuoted(data.quoted);
const mediaData: SendMediaDto = { ...data, quoted: quotedMessage };
if (file) mediaData.media = file.buffer;
public async audioWhatsapp(data: SendAudioDto, file?: any) {
const quotedMessage = this.parseQuoted(data.quoted);
const mediaData: SendAudioDto = { ...data, quoted: quotedMessage };
if (file?.buffer) {
|
||
if (file?.buffer) { | ||
mediaData.audio = file.buffer.toString('base64'); | ||
// mediaData.audio: Buffer = file.buffer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: Commented-out code for audio assignment may cause confusion.
Please remove the commented-out assignment or clarify its purpose to avoid confusion during maintenance.
return await this.sendMessageWithTyping<AnyMessageContent>( | ||
data.number, | ||
{ | ||
audio: isURL(data.audio) ? { url: data.audio } : Buffer.from(data.audio, 'base64'), | ||
audio: isURL(data.audio) ? { url: data.audio } : file?.buffer, | ||
ptt: true, | ||
mimetype: 'audio/ogg; codecs=opus', | ||
}, | ||
{ presence: 'recording', delay: data?.delay }, | ||
{ presence: 'recording', delay: data?.delay, quoted: data?.quoted }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Passing quoted in options may break if not handled by sendMessageWithTyping.
Verify that sendMessageWithTyping and related logic correctly process the quoted field, including cases where it is an object.
Suggested implementation:
public async audioWhatsapp(data: SendAudioDto, file?: any) {
const mediaData: SendAudioDto = { ...data };
data.quoted = this.parseQuoted(data.quoted);
// Ensure quoted is in the correct format for Baileys
let quotedMessage = undefined;
if (data?.quoted) {
// If quoted is an object, validate/transform as needed
if (typeof data.quoted === 'object' && data.quoted.messageId && data.quoted.remoteJid) {
quotedMessage = {
key: {
id: data.quoted.messageId,
remoteJid: data.quoted.remoteJid,
fromMe: data.quoted.fromMe ?? false,
},
message: data.quoted.message,
};
} else {
quotedMessage = data.quoted;
}
}
if (file?.buffer) {
// mediaData.audio: Buffer = file.buffer;
} else if (!isURL(data.audio) && !isBase64(data.audio)) {
console.error('Invalid file or audio source');
throw new BadRequestException('File buffer, URL, or base64 audio is required');
return await this.sendMessageWithTyping<AnyMessageContent>(
data.number,
{
audio: isURL(data.audio) ? { url: data.audio } : file?.buffer,
ptt: true,
mimetype: 'audio/ogg; codecs=opus',
},
{ presence: 'recording', delay: data?.delay, quoted: quotedMessage },
);
}
You must also verify the implementation of sendMessageWithTyping
:
- Ensure it passes the
quoted
field to the underlying BaileyssendMessage
call. - If not, update
sendMessageWithTyping
to do so, and ensure it expectsquoted
in the correct format (usually as a message object). - If
parseQuoted
does not guarantee the correct format, consider moving the transformation logic there instead.
const response = { | ||
id: instanceByName.id, | ||
name: instanceByName.name, | ||
ownerJid: instanceByName.ownerJid, | ||
connectionStatus: instanceByName.connectionStatus, | ||
profileName: instanceByName.profileName, | ||
} | ||
return response; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable
)
const response = { | |
id: instanceByName.id, | |
name: instanceByName.name, | |
ownerJid: instanceByName.ownerJid, | |
connectionStatus: instanceByName.connectionStatus, | |
profileName: instanceByName.profileName, | |
} | |
return response; | |
return { | |
id: instanceByName.id, | |
name: instanceByName.name, | |
ownerJid: instanceByName.ownerJid, | |
connectionStatus: instanceByName.connectionStatus, | |
profileName: instanceByName.profileName, | |
}; | |
Explanation
Something that we often see in people's code is assigning to a result variableand then immediately returning it.
Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.
Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
const count = await this.prismaRepository.instance.count(); | ||
return count; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable
)
const count = await this.prismaRepository.instance.count(); | |
return count; | |
return await this.prismaRepository.instance.count(); | |
Explanation
Something that we often see in people's code is assigning to a result variableand then immediately returning it.
Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.
Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
private messageTextJson(received: any) { | ||
let content: any; | ||
const message = received.messages[0]; | ||
const referral = message.referral; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring
)
const referral = message.referral; | |
const {referral} = message; |
Explanation
Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.From the Airbnb Javascript Style Guide
} else if(received.message?.protocolMessage){ | ||
if(!received.message?.protocolMessage?.editedMessage){ | ||
this.logger.verbose('message rejected'); | ||
return; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (code-quality): Merge nested if conditions (merge-nested-ifs
)
} else if(received.message?.protocolMessage){ | |
if(!received.message?.protocolMessage?.editedMessage){ | |
this.logger.verbose('message rejected'); | |
return; | |
} | |
} | |
} else if (received.message?.protocolMessage && !received.message?.protocolMessage?.editedMessage) { | |
this.logger.verbose('message rejected'); | |
return; | |
} | |
Explanation
Reading deeply nested conditional code is confusing, since you have to keep track of whichconditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two
if
conditions can be combined usingand
is an easy win.
}, | ||
); | ||
|
||
return buffer.toString('base64');; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (code-quality): Remove unreachable code. (remove-unreachable-code
)
return buffer.toString('base64');; | |
return buffer.toString('base64'); |
Explanation
Statements after areturn
, break
, continue
or throw
will never be executed.Leaving them in the code confuses the reader, who may believe that these
statements have some effect. They should therefore be removed.
Summary by Sourcery
Enhance WhatsApp Baileys and Business integrations by adding instance management APIs, full-history delivery via webhooks, quoted message and location support, and streamlined media handling. Update caching, logging, and configuration, comment out legacy persistence logic, bump dependencies, and add related Prisma migrations.
New Features:
Enhancements:
Chores: