FEAT: Parser de eventos do webhook Meta (messages e statuses)
This commit is contained in:
parent
c681814cca
commit
79b55be7ef
@ -1,4 +1,4 @@
|
|||||||
import { Controller, Get, Post, Body, Delete, Param, ForbiddenException, Query, RawBody } from '@nestjs/common';
|
import { Controller, Get, Post, Body, Delete, Param, ForbiddenException, Query } from '@nestjs/common';
|
||||||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { WhatsappService } from './whatsapp.service';
|
import { WhatsappService } from './whatsapp.service';
|
||||||
import { AttendanceAssignmentService } from '../attendance/attendance-assignment.service';
|
import { AttendanceAssignmentService } from '../attendance/attendance-assignment.service';
|
||||||
@ -291,7 +291,7 @@ export class WhatsappController {
|
|||||||
@ApiOperation({ summary: 'Recebe eventos do webhook Meta' })
|
@ApiOperation({ summary: 'Recebe eventos do webhook Meta' })
|
||||||
@Post('webhook')
|
@Post('webhook')
|
||||||
async receiveWebhook(@Body() body: any) {
|
async receiveWebhook(@Body() body: any) {
|
||||||
console.log('Webhook Meta:', JSON.stringify(body, null, 2));
|
await this.whatsappService.handleMetaWebhookEvent(body);
|
||||||
return { status: 'ok' };
|
return { status: 'ok' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -578,4 +578,78 @@ export class WhatsappService implements OnModuleInit {
|
|||||||
return `*Atendente: ${cleanSenderName}*\n\n${cleanMessage}`;
|
return `*Atendente: ${cleanSenderName}*\n\n${cleanMessage}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleMetaWebhookEvent(body: any): Promise<void> {
|
||||||
|
if (body?.object !== 'whatsapp_business_account') return;
|
||||||
|
|
||||||
|
for (const entry of body?.entry ?? []) {
|
||||||
|
for (const change of entry?.changes ?? []) {
|
||||||
|
if (change.field !== 'messages') continue;
|
||||||
|
|
||||||
|
const value = change.value;
|
||||||
|
|
||||||
|
if (value?.messages?.length) {
|
||||||
|
for (const message of value.messages) {
|
||||||
|
await this.handleMetaIncomingMessage(message, value.contacts ?? []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value?.statuses?.length) {
|
||||||
|
for (const status of value.statuses) {
|
||||||
|
this.logger.log(`[Meta] Status de mensagem: ${status.id} → ${status.status} (destinatário: ${status.recipient_id})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleMetaIncomingMessage(message: any, contacts: any[]): Promise<void> {
|
||||||
|
const from: string = message.from;
|
||||||
|
const messageId: string = message.id;
|
||||||
|
const timestamp: number = parseInt(message.timestamp, 10);
|
||||||
|
const messageBody: string = message.text?.body?.trim() ?? '';
|
||||||
|
|
||||||
|
const contact = contacts.find((c) => c.wa_id === from);
|
||||||
|
const contactName: string = contact?.profile?.name || from;
|
||||||
|
|
||||||
|
if (this.processedIncomingMessages.has(messageId)) return;
|
||||||
|
this.processedIncomingMessages.add(messageId);
|
||||||
|
if (this.processedIncomingMessages.size > 1000) {
|
||||||
|
const [oldest] = this.processedIncomingMessages;
|
||||||
|
this.processedIncomingMessages.delete(oldest);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`[Meta] Mensagem recebida de ${from} (${contactName}): ${messageBody || '[mídia]'}`);
|
||||||
|
|
||||||
|
this.gateway.emitNewMessage({
|
||||||
|
from,
|
||||||
|
body: messageBody,
|
||||||
|
timestamp,
|
||||||
|
isGroupMsg: false,
|
||||||
|
id: messageId,
|
||||||
|
fromMe: false,
|
||||||
|
notifyName: contactName,
|
||||||
|
hasMedia: message.type !== 'text',
|
||||||
|
media: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (messageBody) {
|
||||||
|
await this.assignmentService.markCustomerReplied(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!messageBody) {
|
||||||
|
this.logger.log(`[Meta] Triagem ignorada para ${from}: mensagem sem texto.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeMsg = {
|
||||||
|
reply: async (text: string) => {
|
||||||
|
// TODO Fase 2: enviar via Meta Graph API
|
||||||
|
this.logger.warn(`[Meta] Bot reply pendente de implementação. Mensagem para ${from}: "${text}"`);
|
||||||
|
},
|
||||||
|
_data: { notifyName: contactName },
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.enqueueIncomingRoute(from, fakeMsg, messageBody, messageId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user