Feat: Agregar soporte para envío de Contacts, Polls y Events
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m8s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m8s
- Backend: Nuevo soporte en endpoint /send para tipos contact, poll, event - UI: Modales para crear y enviar contactos, encuestas y eventos - Visualización: Componentes MessagePoll y MessageEvent para mostrar mensajes recibidos - Tipos: Agregar PollInfo, EventInfo y tipo 'event' a MessageType
This commit is contained in:
@@ -33,6 +33,43 @@ interface TextMessageBody {
|
||||
quotedMessageId?: string
|
||||
}
|
||||
|
||||
interface ContactInfo {
|
||||
displayName: string
|
||||
phoneNumber: string
|
||||
organization?: string
|
||||
}
|
||||
|
||||
interface ContactMessageBody {
|
||||
type: 'contact'
|
||||
contacts: ContactInfo[]
|
||||
quotedMessageId?: string
|
||||
}
|
||||
|
||||
interface PollMessageBody {
|
||||
type: 'poll'
|
||||
name: string
|
||||
options: string[]
|
||||
selectableCount?: number
|
||||
quotedMessageId?: string
|
||||
}
|
||||
|
||||
interface EventMessageBody {
|
||||
type: 'event'
|
||||
name: string
|
||||
startDate: string // ISO date string
|
||||
endDate?: string
|
||||
description?: string
|
||||
location?: {
|
||||
name?: string
|
||||
address?: string
|
||||
latitude?: number
|
||||
longitude?: number
|
||||
}
|
||||
quotedMessageId?: string
|
||||
}
|
||||
|
||||
type JsonMessageBody = TextMessageBody | ContactMessageBody | PollMessageBody | EventMessageBody
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const username = getHeader(event, 'x-authentik-username')
|
||||
if (!username) {
|
||||
@@ -71,80 +108,107 @@ export default defineEventHandler(async (event) => {
|
||||
// ==================== MEDIA / STICKER ====================
|
||||
return await handleMediaMessage(event, instanceId, chatId, jid, socket)
|
||||
} else {
|
||||
// ==================== TEXT ====================
|
||||
return await handleTextMessage(event, instanceId, chatId, jid)
|
||||
// ==================== JSON MESSAGES ====================
|
||||
return await handleJsonMessage(event, instanceId, chatId, jid, socket)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Handle text message sending
|
||||
* Handle JSON message sending (text, contacts, polls, events)
|
||||
*/
|
||||
async function handleTextMessage(
|
||||
async function handleJsonMessage(
|
||||
event: any,
|
||||
instanceId: string,
|
||||
chatId: string,
|
||||
jid: string
|
||||
jid: string,
|
||||
socket: any
|
||||
) {
|
||||
const body = await readBody<TextMessageBody>(event)
|
||||
const messageText = body.content || body.message
|
||||
const body = await readBody<JsonMessageBody>(event)
|
||||
|
||||
if (!messageText?.trim()) {
|
||||
throw createError({ statusCode: 400, message: 'Message content is required' })
|
||||
}
|
||||
// Determine message type
|
||||
const messageType = (body as any).type || 'text'
|
||||
|
||||
// Get quoted message if provided
|
||||
let quotedMessage = null
|
||||
if (body.quotedMessageId) {
|
||||
const quotedMessageId = (body as any).quotedMessageId
|
||||
if (quotedMessageId) {
|
||||
const quotedResult = await query(
|
||||
'SELECT raw_message FROM messages WHERE message_id = $1 AND instance_id = $2',
|
||||
[body.quotedMessageId, instanceId]
|
||||
[quotedMessageId, instanceId]
|
||||
)
|
||||
if (quotedResult.rows.length > 0) {
|
||||
quotedMessage = quotedResult.rows[0].raw_message
|
||||
}
|
||||
}
|
||||
|
||||
const options: any = {}
|
||||
if (quotedMessage) {
|
||||
options.quoted = quotedMessage
|
||||
}
|
||||
|
||||
try {
|
||||
const content = { text: messageText }
|
||||
const options: any = {}
|
||||
if (quotedMessage) {
|
||||
options.quoted = quotedMessage
|
||||
}
|
||||
let content: any
|
||||
let dbMessageType: string
|
||||
let dbContent: string
|
||||
|
||||
const result = await baileysManager.sendMessage(instanceId, jid, content, options)
|
||||
switch (messageType) {
|
||||
case 'contact':
|
||||
return await handleContactMessage(body as ContactMessageBody, instanceId, chatId, jid, socket, options)
|
||||
|
||||
// Save to database
|
||||
await query(
|
||||
`INSERT INTO messages (
|
||||
instance_id, chat_id, message_id, from_jid, from_me,
|
||||
message_type, content, timestamp, status, raw_message, quoted_message_id
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10)
|
||||
ON CONFLICT (instance_id, message_id) DO NOTHING`,
|
||||
[
|
||||
instanceId,
|
||||
chatId,
|
||||
result.key.id,
|
||||
'me',
|
||||
true,
|
||||
'text',
|
||||
messageText,
|
||||
'sent',
|
||||
JSON.stringify(result),
|
||||
body.quotedMessageId || null
|
||||
]
|
||||
)
|
||||
case 'poll':
|
||||
return await handlePollMessage(body as PollMessageBody, instanceId, chatId, jid, socket, options)
|
||||
|
||||
await query(
|
||||
`UPDATE chats SET last_message_at = NOW(), last_message_type = 'text' WHERE id = $1`,
|
||||
[chatId]
|
||||
)
|
||||
case 'event':
|
||||
return await handleEventMessage(body as EventMessageBody, instanceId, chatId, jid, socket, options)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messages: [{ messageId: result.key.id, type: 'text' }]
|
||||
default:
|
||||
// Text message
|
||||
const textBody = body as TextMessageBody
|
||||
const messageText = textBody.content || textBody.message
|
||||
|
||||
if (!messageText?.trim()) {
|
||||
throw createError({ statusCode: 400, message: 'Message content is required' })
|
||||
}
|
||||
|
||||
content = { text: messageText }
|
||||
dbMessageType = 'text'
|
||||
dbContent = messageText
|
||||
|
||||
const result = await socket.sendMessage(jid, content, options)
|
||||
|
||||
// Save to database
|
||||
await query(
|
||||
`INSERT INTO messages (
|
||||
instance_id, chat_id, message_id, from_jid, from_me,
|
||||
message_type, content, timestamp, status, raw_message, quoted_message_id
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10)
|
||||
ON CONFLICT (instance_id, message_id) DO NOTHING`,
|
||||
[
|
||||
instanceId,
|
||||
chatId,
|
||||
result.key.id,
|
||||
'me',
|
||||
true,
|
||||
dbMessageType,
|
||||
dbContent,
|
||||
'sent',
|
||||
JSON.stringify(result),
|
||||
quotedMessageId || null
|
||||
]
|
||||
)
|
||||
|
||||
await query(
|
||||
`UPDATE chats SET last_message_at = NOW(), last_message_type = $1 WHERE id = $2`,
|
||||
[dbMessageType, chatId]
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messages: [{ messageId: result.key.id, type: dbMessageType }]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Send] Error sending text:', error)
|
||||
console.error('[Send] Error sending message:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: `Failed to send message: ${(error as Error).message}`
|
||||
@@ -152,6 +216,231 @@ async function handleTextMessage(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle contact message sending
|
||||
*/
|
||||
async function handleContactMessage(
|
||||
body: ContactMessageBody,
|
||||
instanceId: string,
|
||||
chatId: string,
|
||||
jid: string,
|
||||
socket: any,
|
||||
options: any
|
||||
) {
|
||||
if (!body.contacts || body.contacts.length === 0) {
|
||||
throw createError({ statusCode: 400, message: 'At least one contact is required' })
|
||||
}
|
||||
|
||||
// Build vCards for each contact
|
||||
const vcards = body.contacts.map(contact => {
|
||||
const lines = [
|
||||
'BEGIN:VCARD',
|
||||
'VERSION:3.0',
|
||||
`FN:${contact.displayName}`,
|
||||
`TEL;type=CELL;waid=${contact.phoneNumber.replace(/\D/g, '')}:${contact.phoneNumber}`
|
||||
]
|
||||
|
||||
if (contact.organization) {
|
||||
lines.push(`ORG:${contact.organization}`)
|
||||
}
|
||||
|
||||
lines.push('END:VCARD')
|
||||
return lines.join('\n')
|
||||
})
|
||||
|
||||
const content = {
|
||||
contacts: {
|
||||
displayName: body.contacts.length === 1
|
||||
? body.contacts[0].displayName
|
||||
: `${body.contacts.length} contactos`,
|
||||
contacts: vcards.map(vcard => ({ vcard }))
|
||||
}
|
||||
}
|
||||
|
||||
const result = await socket.sendMessage(jid, content, options)
|
||||
|
||||
// Save to database
|
||||
await query(
|
||||
`INSERT INTO messages (
|
||||
instance_id, chat_id, message_id, from_jid, from_me,
|
||||
message_type, content, timestamp, status, raw_message, quoted_message_id
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10)
|
||||
ON CONFLICT (instance_id, message_id) DO NOTHING`,
|
||||
[
|
||||
instanceId,
|
||||
chatId,
|
||||
result.key.id,
|
||||
'me',
|
||||
true,
|
||||
'contact',
|
||||
JSON.stringify(body.contacts),
|
||||
'sent',
|
||||
JSON.stringify(result),
|
||||
body.quotedMessageId || null
|
||||
]
|
||||
)
|
||||
|
||||
await query(
|
||||
`UPDATE chats SET last_message_at = NOW(), last_message_type = 'contact' WHERE id = $1`,
|
||||
[chatId]
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messages: [{ messageId: result.key.id, type: 'contact' }]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle poll message sending
|
||||
*/
|
||||
async function handlePollMessage(
|
||||
body: PollMessageBody,
|
||||
instanceId: string,
|
||||
chatId: string,
|
||||
jid: string,
|
||||
socket: any,
|
||||
options: any
|
||||
) {
|
||||
if (!body.name?.trim()) {
|
||||
throw createError({ statusCode: 400, message: 'Poll name is required' })
|
||||
}
|
||||
|
||||
if (!body.options || body.options.length < 2) {
|
||||
throw createError({ statusCode: 400, message: 'At least 2 poll options are required' })
|
||||
}
|
||||
|
||||
if (body.options.length > 12) {
|
||||
throw createError({ statusCode: 400, message: 'Maximum 12 poll options allowed' })
|
||||
}
|
||||
|
||||
const content = {
|
||||
poll: {
|
||||
name: body.name,
|
||||
values: body.options,
|
||||
selectableCount: body.selectableCount || 1
|
||||
}
|
||||
}
|
||||
|
||||
const result = await socket.sendMessage(jid, content, options)
|
||||
|
||||
// Save to database
|
||||
await query(
|
||||
`INSERT INTO messages (
|
||||
instance_id, chat_id, message_id, from_jid, from_me,
|
||||
message_type, content, timestamp, status, raw_message, quoted_message_id
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10)
|
||||
ON CONFLICT (instance_id, message_id) DO NOTHING`,
|
||||
[
|
||||
instanceId,
|
||||
chatId,
|
||||
result.key.id,
|
||||
'me',
|
||||
true,
|
||||
'poll',
|
||||
JSON.stringify({ name: body.name, options: body.options, selectableCount: body.selectableCount || 1 }),
|
||||
'sent',
|
||||
JSON.stringify(result),
|
||||
body.quotedMessageId || null
|
||||
]
|
||||
)
|
||||
|
||||
await query(
|
||||
`UPDATE chats SET last_message_at = NOW(), last_message_type = 'poll' WHERE id = $1`,
|
||||
[chatId]
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messages: [{ messageId: result.key.id, type: 'poll' }]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle event message sending
|
||||
*/
|
||||
async function handleEventMessage(
|
||||
body: EventMessageBody,
|
||||
instanceId: string,
|
||||
chatId: string,
|
||||
jid: string,
|
||||
socket: any,
|
||||
options: any
|
||||
) {
|
||||
if (!body.name?.trim()) {
|
||||
throw createError({ statusCode: 400, message: 'Event name is required' })
|
||||
}
|
||||
|
||||
if (!body.startDate) {
|
||||
throw createError({ statusCode: 400, message: 'Event start date is required' })
|
||||
}
|
||||
|
||||
const eventContent: any = {
|
||||
name: body.name,
|
||||
startDate: new Date(body.startDate)
|
||||
}
|
||||
|
||||
if (body.endDate) {
|
||||
eventContent.endDate = new Date(body.endDate)
|
||||
}
|
||||
|
||||
if (body.description) {
|
||||
eventContent.description = body.description
|
||||
}
|
||||
|
||||
if (body.location) {
|
||||
if (body.location.latitude && body.location.longitude) {
|
||||
eventContent.location = {
|
||||
degreesLatitude: body.location.latitude,
|
||||
degreesLongitude: body.location.longitude,
|
||||
name: body.location.name,
|
||||
address: body.location.address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const content = { event: eventContent }
|
||||
|
||||
const result = await socket.sendMessage(jid, content, options)
|
||||
|
||||
// Save to database
|
||||
await query(
|
||||
`INSERT INTO messages (
|
||||
instance_id, chat_id, message_id, from_jid, from_me,
|
||||
message_type, content, timestamp, status, raw_message, quoted_message_id
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10)
|
||||
ON CONFLICT (instance_id, message_id) DO NOTHING`,
|
||||
[
|
||||
instanceId,
|
||||
chatId,
|
||||
result.key.id,
|
||||
'me',
|
||||
true,
|
||||
'event',
|
||||
JSON.stringify({
|
||||
name: body.name,
|
||||
startDate: body.startDate,
|
||||
endDate: body.endDate,
|
||||
description: body.description,
|
||||
location: body.location
|
||||
}),
|
||||
'sent',
|
||||
JSON.stringify(result),
|
||||
body.quotedMessageId || null
|
||||
]
|
||||
)
|
||||
|
||||
await query(
|
||||
`UPDATE chats SET last_message_at = NOW(), last_message_type = 'event' WHERE id = $1`,
|
||||
[chatId]
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messages: [{ messageId: result.key.id, type: 'event' }]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle media/sticker message sending
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user