Feature: Agregar botón para crear webhook de debug automáticamente
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
- Agregar botón "Crear Webhook de Debug" en WebhookReceiverSection - Detectar si ya existe un webhook apuntando al receptor de debug - Permitir eliminar el webhook de debug - Incluir todos los eventos disponibles al crear el webhook - También incluye mejoras previas de manejo de media y mensajes
This commit is contained in:
234
server/api/messages/[instanceId]/[chatId]/send-media.post.ts
Normal file
234
server/api/messages/[instanceId]/[chatId]/send-media.post.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
// Add quoted message if exists
|
||||
if (quotedMessage) {
|
||||
(content as any).quoted = quotedMessage
|
||||
}
|
||||
|
||||
// Send message
|
||||
console.log(`[SendMedia] Sending ${mediaType} to ${jid}`)
|
||||
const result = await socket.sendMessage(jid, content)
|
||||
|
||||
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]
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user