mail_thread.py 5.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>
  19. #
  20. ##############################################################################
  21. import logging
  22. import re
  23. from openerp.addons.mail.mail_message import decode
  24. from openerp.addons.mail.mail_thread import decode_header
  25. from openerp.osv import osv
  26. _logger = logging.getLogger(__name__)
  27. class MailThread(osv.AbstractModel):
  28. """ Update MailThread to add the feature of bounced emails and replied emails
  29. in message_process. """
  30. _name = 'mail.thread'
  31. _inherit = ['mail.thread']
  32. def message_route_check_bounce(self, cr, uid, message, context=None):
  33. """ Override to verify that the email_to is the bounce alias. If it is the
  34. case, log the bounce, set the parent and related document as bounced and
  35. return False to end the routing process. """
  36. bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
  37. message_id = message.get('Message-Id')
  38. email_from = decode_header(message, 'From')
  39. email_to = decode_header(message, 'To')
  40. # 0. Verify whether this is a bounced email (wrong destination,...) -> use it to collect data, such as dead leads
  41. if bounce_alias and bounce_alias in email_to:
  42. # Bounce regex
  43. # Typical form of bounce is bounce_alias-128-crm.lead-34@domain
  44. # group(1) = the mail ID; group(2) = the model (if any); group(3) = the record ID
  45. bounce_re = re.compile("%s-(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias), re.UNICODE)
  46. bounce_match = bounce_re.search(email_to)
  47. if bounce_match:
  48. bounced_model, bounced_thread_id = None, False
  49. bounced_mail_id = bounce_match.group(1)
  50. stat_ids = self.pool['mail.mail.statistics'].set_bounced(cr, uid, mail_mail_ids=[bounced_mail_id], context=context)
  51. for stat in self.pool['mail.mail.statistics'].browse(cr, uid, stat_ids, context=context):
  52. bounced_model = stat.model
  53. bounced_thread_id = stat.res_id
  54. _logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s',
  55. email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id)
  56. if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce') and bounced_thread_id:
  57. self.pool[bounced_model].message_receive_bounce(cr, uid, [bounced_thread_id], mail_id=bounced_mail_id, context=context)
  58. return False
  59. return True
  60. def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
  61. custom_values=None, context=None):
  62. if not self.message_route_check_bounce(cr, uid, message, context=context):
  63. return []
  64. return super(MailThread, self).message_route(cr, uid, message, message_dict, model, thread_id, custom_values, context)
  65. def message_receive_bounce(self, cr, uid, ids, mail_id=None, context=None):
  66. """Called by ``message_process`` when a bounce email (such as Undelivered
  67. Mail Returned to Sender) is received for an existing thread. The default
  68. behavior is to check is an integer ``message_bounce`` column exists.
  69. If it is the case, its content is incremented. """
  70. if 'message_bounce' in self._fields:
  71. for obj in self.browse(cr, uid, ids, context=context):
  72. self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
  73. def message_route_process(self, cr, uid, message, message_dict, routes, context=None):
  74. """ Override to update the parent mail statistics. The parent is found
  75. by using the References header of the incoming message and looking for
  76. matching message_id in mail.mail.statistics. """
  77. if message.get('References'):
  78. message_ids = [x.strip() for x in decode(message['References']).split()]
  79. self.pool['mail.mail.statistics'].set_replied(cr, uid, mail_message_ids=message_ids, context=context)
  80. return super(MailThread, self).message_route_process(cr, uid, message, message_dict, routes, context=context)