-
Notifications
You must be signed in to change notification settings - Fork 5k
fix: handle messageContextInfo in media upload to prevent MinIO errors #2273
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
fix: handle messageContextInfo in media upload to prevent MinIO errors #2273
Conversation
Reviewer's guide (collapsed on small PRs)Reviewer's GuideHandles WhatsApp messages that contain only messageContextInfo without media by treating them as non-media and short-circuiting MinIO upload, converting a previous throw into a null return and adding guard checks and verbose logs around media extraction and upload paths. Sequence diagram for WhatsApp media upload with messageContextInfo handlingsequenceDiagram
actor WhatsAppServer
participant BaileysStartupService
participant getBase64FromMediaMessage
participant MinIO
participant Logger
WhatsAppServer ->> BaileysStartupService: onMediaMessage message
BaileysStartupService ->> getBase64FromMediaMessage: getBase64FromMediaMessage message, isFromBaileys
getBase64FromMediaMessage -->> BaileysStartupService: mediaOrNull
alt mediaOrNull is null (messageContextInfo only)
BaileysStartupService ->> Logger: verbose [No valid media to upload (messageContextInfo only), skipping MinIO]
BaileysStartupService -->> WhatsAppServer: continue without MinIO upload
else valid media object
BaileysStartupService ->> MinIO: upload buffer, mediaType, fileName, size, mimetype
MinIO -->> BaileysStartupService: uploadResult
BaileysStartupService -->> WhatsAppServer: processing completed
end
Flow diagram for getBase64FromMediaMessage messageContextInfo handlingflowchart TD
A[Receive message in getBase64FromMediaMessage] --> B[Check if message contains messageContextInfo key]
B -->|No| C[Proceed to media extraction]
C --> D[Decode media binary data]
D --> E[Build media object buffer, mediaType, fileName, size]
E --> F[Return media object]
B -->|Yes| G[Check if message has only messageContextInfo field]
G -->|No| C
G -->|Yes| H[Logger verbose Message contains only messageContextInfo, skipping media processing]
H --> I[Return null to caller]
File-Level Changes
Assessment against linked issues
Possibly linked issues
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 there - I've reviewed your changes - here's some feedback:
- Now that
getBase64FromMediaMessagecan returnnull, its return type and any related typings/interfaces should be updated to explicitly reflect the nullable result so future callers are forced to handle this case. - The new
if (!media) return;checks inside the upload logic change the control flow; verify that returning from these scopes is intended (i.e., that it only skips processing of the current message and not the entire higher-level operation), and consider using a more localized guard (e.g.,continuein loops or earlyifblocks) if needed. - The verbose log message for skipping messageContextInfo-only media is duplicated in two places; consider extracting it into a shared helper or constant to keep the behavior and wording consistent if it needs to change later.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Now that `getBase64FromMediaMessage` can return `null`, its return type and any related typings/interfaces should be updated to explicitly reflect the nullable result so future callers are forced to handle this case.
- The new `if (!media) return;` checks inside the upload logic change the control flow; verify that returning from these scopes is intended (i.e., that it only skips processing of the current message and not the entire higher-level operation), and consider using a more localized guard (e.g., `continue` in loops or early `if` blocks) if needed.
- The verbose log message for skipping messageContextInfo-only media is duplicated in two places; consider extracting it into a shared helper or constant to keep the behavior and wording consistent if it needs to change later.
## Individual Comments
### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:3700-3701` </location>
<code_context>
if ('messageContextInfo' in msg.message && Object.keys(msg.message).length === 1) {
- throw 'The message is messageContextInfo';
+ this.logger.verbose('Message contains only messageContextInfo, skipping media processing');
+ return null;
}
</code_context>
<issue_to_address>
**question (bug_risk):** Changing from throwing to returning `null` alters error semantics and caller expectations.
Previously this code threw (`'The message is messageContextInfo'`), which would surface as an error. Now it only logs (verbose) and returns `null`. Any remaining callers that still assume a non-null return (e.g. destructure or use `media` as a resolved object) may now misbehave. Please audit all `getBase64FromMediaMessage` usages to ensure they handle `null`, or consider a more explicit result type (e.g. `{ kind: 'no-media' }`) to make this state safer to consume.
</issue_to_address>
### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:1282-1292` </location>
<code_context>
if (isMedia) {
if (this.configService.get<S3>('S3').ENABLE) {
try {
if (isVideo && !this.configService.get<S3>('S3').SAVE_VIDEO) {
this.logger.warn('Video upload is disabled. Skipping video upload.');
// Skip video upload by returning early from this block
return;
}
const message: any = received;
// Verificação adicional para garantir que há conteúdo de mídia real
const hasRealMedia = this.hasValidMediaContent(message);
if (!hasRealMedia) {
this.logger.warn('Message detected as media but contains no valid media content');
} else {
const media = await this.getBase64FromMediaMessage({ message }, true);
if (!media) {
this.logger.verbose('No valid media to upload (messageContextInfo only), skipping MinIO');
return;
}
const { buffer, mediaType, fileName, size } = media;
const mimetype = mimeTypes.lookup(fileName).toString();
const fullName = join(
`${this.instance.id}`,
received.key.remoteJid,
mediaType,
`${Date.now()}_${fileName}`,
);
await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, { 'Content-Type': mimetype });
await this.prismaRepository.media.create({
data: {
messageId: msg.id,
instanceId: this.instanceId,
type: mediaType,
fileName: fullName,
mimetype,
},
});
const mediaUrl = await s3Service.getObjectUrl(fullName);
messageRaw.message.mediaUrl = mediaUrl;
await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
}
} catch (error) {
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
}
}
}
</code_context>
<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/merge-nested-ifs))
```suggestion
if (isMedia && this.configService.get<S3>('S3').ENABLE) {
try {
if (isVideo && !this.configService.get<S3>('S3').SAVE_VIDEO) {
this.logger.warn('Video upload is disabled. Skipping video upload.');
// Skip video upload by returning early from this block
return;
}
const message: any = received;
// Verificação adicional para garantir que há conteúdo de mídia real
const hasRealMedia = this.hasValidMediaContent(message);
if (!hasRealMedia) {
this.logger.warn('Message detected as media but contains no valid media content');
} else {
const media = await this.getBase64FromMediaMessage({ message }, true);
if (!media) {
this.logger.verbose('No valid media to upload (messageContextInfo only), skipping MinIO');
return;
}
const { buffer, mediaType, fileName, size } = media;
const mimetype = mimeTypes.lookup(fileName).toString();
const fullName = join(
`${this.instance.id}`,
received.key.remoteJid,
mediaType,
`${Date.now()}_${fileName}`,
);
await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, { 'Content-Type': mimetype });
await this.prismaRepository.media.create({
data: {
messageId: msg.id,
instanceId: this.instanceId,
type: mediaType,
fileName: fullName,
mimetype,
},
});
const mediaUrl = await s3Service.getObjectUrl(fullName);
messageRaw.message.mediaUrl = mediaUrl;
await this.prismaRepository.message.update({ where: { id: msg.id }, data: messageRaw });
}
} catch (error) {
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
}
}
```
<br/><details><summary>Explanation</summary>Reading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
Resolveu aqui, muito obrigado! |
|
Boa! Deu certo! Obrigado!! |
|
Issue scaled significantly. Apparently, not only with viewOnce chats anymore. We need to handle in a more robust way, because most of the times, a standalone call to /getBase64FromMediaMessage works to get the base64 and display media properly on Evolution Manager or handle it for S3/MinIO.. Anyone have managed to get around this? |
|
Please, fix the lint with |
This PR fixes the
Error on upload file to minioerror that occurs when processing messages containing onlymessageContextInfowithout actual media content.Problem
When WhatsApp sends media messages (audio, video, images) with ephemeral/viewOnce wrappers, the message structure gets transformed during processing. After the
MessageSubtypeloop transformation, some messages end up containing onlymessageContextInfometadata without the actual media binary data. The previous code wouldthrowan error in thiscase, causing thousands of error logs like:
[
'Error on upload file to minio',
[ 'The message is messageContextInfo' ],
undefined
]
Solution
throw 'The message is messageContextInfo'toreturn nullingetBase64FromMediaMessage()functionChanges Made
getBase64FromMediaMessage(): Returnsnullinstead of throwing when message contains onlymessageContextInfoif (!media) returncheckif (!media) returncheck🔗 Related Issue
Closes #1026
Related: #1061, #1585, #1872
🧪 Type of Change
🧪 Testing
Testing Details
messageContextInfoare now skipped gracefully📸 Screenshots (if applicable)
Before (error logs):
[ERROR] [ChannelStartupService] Error processing media message:
[ERROR] [ChannelStartupService] The message is messageContextInfo
[
'Error on upload file to minio',
[ 'The message is messageContextInfo' ],
undefined
]
After (clean logs):
[VERBOSE] [BaileysStartupService] Message contains only messageContextInfo, skipping media processing
✅ Checklist
📝 Additional Notes
Root Cause Analysis
The issue occurs due to the interaction between:
This creates a race condition where a message passes the initial media check but fails after transformation.
Backward Compatibility
Summary by Sourcery
Gracefully skip WhatsApp messages that contain only messageContextInfo during media handling to avoid unnecessary MinIO upload errors and noisy logs.
Bug Fixes:
Enhancements: