/** * 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] ) }