feat(odoo): add WhatsApp webhook controller
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
140
odoo_whatsapp_hub/controllers/webhook.py
Normal file
140
odoo_whatsapp_hub/controllers/webhook.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
from odoo import http
|
||||||
|
from odoo.http import request
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WhatsAppWebhookController(http.Controller):
|
||||||
|
|
||||||
|
@http.route('/whatsapp/webhook', type='json', auth='public', methods=['POST'], csrf=False)
|
||||||
|
def webhook(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Receive webhooks from WhatsApp Central.
|
||||||
|
Events: message, status_update, conversation_update
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = request.jsonrequest
|
||||||
|
event_type = data.get('type')
|
||||||
|
|
||||||
|
_logger.info(f'WhatsApp webhook received: {event_type}')
|
||||||
|
|
||||||
|
handlers = {
|
||||||
|
'message': self._handle_message,
|
||||||
|
'status_update': self._handle_status_update,
|
||||||
|
'conversation_update': self._handle_conversation_update,
|
||||||
|
'account_status': self._handle_account_status,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = handlers.get(event_type)
|
||||||
|
if handler:
|
||||||
|
return handler(data)
|
||||||
|
|
||||||
|
return {'status': 'ignored', 'reason': f'Unknown event type: {event_type}'}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f'WhatsApp webhook error: {e}')
|
||||||
|
return {'status': 'error', 'message': str(e)}
|
||||||
|
|
||||||
|
def _handle_message(self, data):
|
||||||
|
"""Handle incoming message"""
|
||||||
|
msg_data = data.get('data', {})
|
||||||
|
account_external_id = data.get('account_id')
|
||||||
|
conversation_external_id = msg_data.get('conversation_id')
|
||||||
|
|
||||||
|
account = request.env['whatsapp.account'].sudo().search([
|
||||||
|
('external_id', '=', account_external_id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if not account:
|
||||||
|
return {'status': 'ignored', 'reason': 'Account not found'}
|
||||||
|
|
||||||
|
conversation = request.env['whatsapp.conversation'].sudo().search([
|
||||||
|
('external_id', '=', conversation_external_id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if not conversation:
|
||||||
|
phone = msg_data.get('from', '').split('@')[0]
|
||||||
|
conversation = request.env['whatsapp.conversation'].sudo().create({
|
||||||
|
'external_id': conversation_external_id,
|
||||||
|
'account_id': account.id,
|
||||||
|
'phone_number': phone,
|
||||||
|
'contact_name': msg_data.get('contact_name'),
|
||||||
|
'status': 'bot',
|
||||||
|
})
|
||||||
|
|
||||||
|
partner = request.env['res.partner'].sudo().search([
|
||||||
|
'|',
|
||||||
|
('phone', 'ilike', phone[-10:]),
|
||||||
|
('mobile', 'ilike', phone[-10:]),
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if partner:
|
||||||
|
conversation.partner_id = partner
|
||||||
|
|
||||||
|
request.env['whatsapp.message'].sudo().create({
|
||||||
|
'external_id': msg_data.get('id'),
|
||||||
|
'conversation_id': conversation.id,
|
||||||
|
'direction': 'inbound',
|
||||||
|
'message_type': msg_data.get('type', 'text'),
|
||||||
|
'content': msg_data.get('content'),
|
||||||
|
'media_url': msg_data.get('media_url'),
|
||||||
|
'status': 'delivered',
|
||||||
|
})
|
||||||
|
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
def _handle_status_update(self, data):
|
||||||
|
"""Handle message status update"""
|
||||||
|
msg_data = data.get('data', {})
|
||||||
|
external_id = msg_data.get('message_id')
|
||||||
|
new_status = msg_data.get('status')
|
||||||
|
|
||||||
|
message = request.env['whatsapp.message'].sudo().search([
|
||||||
|
('external_id', '=', external_id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if message:
|
||||||
|
message.write({'status': new_status})
|
||||||
|
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
def _handle_conversation_update(self, data):
|
||||||
|
"""Handle conversation status update"""
|
||||||
|
conv_data = data.get('data', {})
|
||||||
|
external_id = conv_data.get('conversation_id')
|
||||||
|
new_status = conv_data.get('status')
|
||||||
|
|
||||||
|
conversation = request.env['whatsapp.conversation'].sudo().search([
|
||||||
|
('external_id', '=', external_id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if conversation:
|
||||||
|
conversation.write({'status': new_status})
|
||||||
|
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
def _handle_account_status(self, data):
|
||||||
|
"""Handle account status change"""
|
||||||
|
acc_data = data.get('data', {})
|
||||||
|
external_id = data.get('account_id')
|
||||||
|
new_status = acc_data.get('status')
|
||||||
|
|
||||||
|
account = request.env['whatsapp.account'].sudo().search([
|
||||||
|
('external_id', '=', external_id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if account:
|
||||||
|
account.write({
|
||||||
|
'status': new_status,
|
||||||
|
'phone_number': acc_data.get('phone_number'),
|
||||||
|
'qr_code': acc_data.get('qr_code'),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
@http.route('/whatsapp/webhook/test', type='http', auth='public', methods=['GET'])
|
||||||
|
def webhook_test(self):
|
||||||
|
"""Test endpoint to verify webhook connectivity"""
|
||||||
|
return json.dumps({'status': 'ok', 'message': 'WhatsApp webhook is active'})
|
||||||
Reference in New Issue
Block a user