Files
whatsappNucleo/server/api/messages/[instanceId]/[chatId]/send-media.post.ts
josedario87 09d3c5398a
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m8s
Fix: Pasar quoted como opción de Baileys, no dentro del content
El campo quoted debe ir en el tercer parámetro (opciones) de sendMessage,
no dentro del objeto content. Esto corrige el envío de respuestas.
2025-12-03 11:07:06 -06:00

236 lines
6.3 KiB
TypeScript

/**
* POST /api/messages/:instanceId/:chatId/send-media
* Send media messages (images, videos, audio, documents)
*/
import { prepareWAMessageMedia, type AnyMediaMessageContent } from '@whiskeysockets/baileys'
import { baileysManager } from '../../../../services/baileys/manager'
import { query } from '../../../../utils/database'
// Max file sizes (in bytes)
const MAX_SIZES = {
image: 16 * 1024 * 1024, // 16 MB
video: 64 * 1024 * 1024, // 64 MB
audio: 16 * 1024 * 1024, // 16 MB
document: 100 * 1024 * 1024, // 100 MB
}
// MIME type to media type mapping
function getMediaType(mimetype: string): 'image' | 'video' | 'audio' | 'document' | null {
if (mimetype.startsWith('image/')) return 'image'
if (mimetype.startsWith('video/')) return 'video'
if (mimetype.startsWith('audio/')) return 'audio'
// Everything else is a document
return 'document'
}
export default defineEventHandler(async (event) => {
const username = getHeader(event, 'x-authentik-username')
if (!username) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
const instanceId = getRouterParam(event, 'instanceId')
const chatId = getRouterParam(event, 'chatId')
if (!instanceId || !chatId) {
throw createError({ statusCode: 400, message: 'Missing instanceId or chatId' })
}
// Get chat JID
const chatResult = await query(
'SELECT jid FROM chats WHERE id = $1 AND instance_id = $2',
[chatId, instanceId]
)
if (chatResult.rows.length === 0) {
throw createError({ statusCode: 404, message: 'Chat not found' })
}
const jid = chatResult.rows[0].jid
// Get socket
const socket = baileysManager.getSocket(instanceId)
if (!socket) {
throw createError({ statusCode: 400, message: 'Instance not connected' })
}
// Parse multipart form data
const formData = await readMultipartFormData(event)
if (!formData) {
throw createError({ statusCode: 400, message: 'No form data received' })
}
// Extract fields
let caption = ''
let quotedMessageId = ''
let isPtt = false
const files: { name: string; data: Buffer; type: string }[] = []
for (const item of formData) {
if (item.name === 'caption' && item.data) {
caption = item.data.toString()
} else if (item.name === 'quotedMessageId' && item.data) {
quotedMessageId = item.data.toString()
} else if (item.name === 'isPtt' && item.data) {
isPtt = item.data.toString() === 'true'
} else if (item.name === 'files' || item.name === 'file') {
if (item.data && item.type) {
files.push({
name: item.filename || 'file',
data: item.data,
type: item.type
})
}
}
}
if (files.length === 0) {
throw createError({ statusCode: 400, message: 'No files provided' })
}
// Get quoted message if provided
let quotedMessage = null
if (quotedMessageId) {
const quotedResult = await query(
'SELECT raw_message FROM messages WHERE message_id = $1 AND instance_id = $2',
[quotedMessageId, instanceId]
)
if (quotedResult.rows.length > 0) {
quotedMessage = quotedResult.rows[0].raw_message
}
}
const sentMessages = []
// Send each file
for (const file of files) {
const mediaType = getMediaType(file.type)
if (!mediaType) {
console.warn(`[SendMedia] Unknown media type: ${file.type}`)
continue
}
// Check file size
const maxSize = MAX_SIZES[mediaType]
if (file.data.length > maxSize) {
throw createError({
statusCode: 400,
message: `File ${file.name} exceeds maximum size for ${mediaType}`
})
}
try {
// Prepare media content
let content: AnyMediaMessageContent
if (mediaType === 'image') {
content = {
image: file.data,
caption: caption || undefined,
mimetype: file.type as any
}
} else if (mediaType === 'video') {
content = {
video: file.data,
caption: caption || undefined,
mimetype: file.type as any
}
} else if (mediaType === 'audio') {
content = {
audio: file.data,
ptt: isPtt,
mimetype: file.type as any
}
} else {
// Document
content = {
document: file.data,
fileName: file.name,
caption: caption || undefined,
mimetype: file.type as any
}
}
// Build options with quoted message if exists
const options: any = {}
if (quotedMessage) {
options.quoted = quotedMessage
}
// Send message
console.log(`[SendMedia] Sending ${mediaType} to ${jid}`)
const result = await socket.sendMessage(jid, content, options)
if (result) {
sentMessages.push({
messageId: result.key.id,
type: mediaType,
filename: file.name
})
// Save to database
await saveMediaMessage(instanceId, chatId, jid, result, mediaType, caption, file.name)
}
// Only use caption for first file
caption = ''
} catch (error) {
console.error(`[SendMedia] Error sending ${file.name}:`, error)
throw createError({
statusCode: 500,
message: `Error sending ${file.name}`
})
}
}
return {
success: true,
messages: sentMessages
}
})
// Helper to save media message to database
async function saveMediaMessage(
instanceId: string,
chatId: string,
jid: string,
result: any,
messageType: string,
caption: string,
filename: string
) {
const messageId = result.key.id
const timestamp = new Date()
await query(
`INSERT INTO messages (
instance_id, chat_id, message_id, from_jid, to_jid,
from_me, message_type, content, caption, media_filename,
timestamp, status, raw_message
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
ON CONFLICT (instance_id, message_id) DO NOTHING`,
[
instanceId,
chatId,
messageId,
jid, // from_jid will be our JID
jid, // to_jid
true, // from_me
messageType,
caption || null,
caption || null,
filename,
timestamp,
'sent',
JSON.stringify(result)
]
)
// Update chat last message
await query(
`UPDATE chats SET last_message_at = $1, last_message_type = $2 WHERE id = $3`,
[timestamp, messageType, chatId]
)
}