img inline using eml convert then mail
This commit is contained in:
parent
45ab7611a3
commit
5d4707df02
1 changed files with 105 additions and 65 deletions
|
|
@ -5,6 +5,11 @@ import base64
|
||||||
import logging
|
import logging
|
||||||
from odoo.tools.translate import _
|
from odoo.tools.translate import _
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.image import MIMEImage
|
||||||
|
from email.mime.base import MIMEBase
|
||||||
|
from email import encoders
|
||||||
import lxml.html
|
import lxml.html
|
||||||
import re
|
import re
|
||||||
from odoo.tools import email_normalize, email_split
|
from odoo.tools import email_normalize, email_split
|
||||||
|
|
@ -49,8 +54,103 @@ class IrAttachment(models.Model):
|
||||||
# Replace any combination of whitespace (including newlines) with a single space
|
# Replace any combination of whitespace (including newlines) with a single space
|
||||||
return ' '.join(str(value).split())
|
return ' '.join(str(value).split())
|
||||||
|
|
||||||
|
def _msg_to_eml(self, msg_data):
|
||||||
|
"""Convert MSG file data to EML format."""
|
||||||
|
try:
|
||||||
|
# Read the MSG file
|
||||||
|
msg_file = extract_msg.Message(msg_data)
|
||||||
|
_logger.info('Converting MSG to EML - Subject: %s, From: %s, To: %s',
|
||||||
|
msg_file.subject, msg_file.sender, msg_file.to)
|
||||||
|
|
||||||
|
# Create email message
|
||||||
|
email_msg = MIMEMultipart('related')
|
||||||
|
|
||||||
|
# Add headers
|
||||||
|
email_msg['Subject'] = self._clean_header_value(msg_file.subject) or "No Subject"
|
||||||
|
email_msg['From'] = self._clean_header_value(msg_file.sender)
|
||||||
|
email_msg['To'] = self._clean_header_value(msg_file.to)
|
||||||
|
email_msg['Cc'] = self._clean_header_value(msg_file.cc) if msg_file.cc else None
|
||||||
|
email_msg['Date'] = msg_file.date.strftime('%a, %d %b %Y %H:%M:%S %z') if msg_file.date else formatdate(localtime=True)
|
||||||
|
|
||||||
|
# Add original MSG headers if present
|
||||||
|
for header in ['Message-ID', 'In-Reply-To', 'References']:
|
||||||
|
if header.lower() in msg_file.header:
|
||||||
|
header_value = self._clean_header_value(msg_file.header[header.lower()])
|
||||||
|
if header_value: # Only add header if it has a value
|
||||||
|
email_msg[header] = header_value
|
||||||
|
|
||||||
|
# Set the body
|
||||||
|
if msg_file.body:
|
||||||
|
body = msg_file.body
|
||||||
|
|
||||||
|
# Si ce n'est pas déjà du HTML mais contient des CIDs, convertir en HTML
|
||||||
|
if '<html' not in body.lower() and '[cid:' in body:
|
||||||
|
# Convertir les retours à la ligne en <br>
|
||||||
|
body = body.replace('\n', '<br>')
|
||||||
|
# Convertir les références CID en balises img
|
||||||
|
import re
|
||||||
|
body = re.sub(
|
||||||
|
r'\[cid:([^\]]+)\]',
|
||||||
|
lambda m: f'<img src="cid:{m.group(1)}" alt="{m.group(1)}">',
|
||||||
|
body
|
||||||
|
)
|
||||||
|
# Envelopper dans du HTML
|
||||||
|
body = f'<html><body>{body}</body></html>'
|
||||||
|
|
||||||
|
# Clean HTML if needed
|
||||||
|
if '<html' in body.lower():
|
||||||
|
body = html_sanitize(body)
|
||||||
|
|
||||||
|
html_part = MIMEText(body, 'html')
|
||||||
|
email_msg.attach(html_part)
|
||||||
|
else:
|
||||||
|
html_part = MIMEText('', 'html')
|
||||||
|
email_msg.attach(html_part)
|
||||||
|
|
||||||
|
# Add attachments
|
||||||
|
for attachment in msg_file.attachments:
|
||||||
|
if not hasattr(attachment, 'data') or not attachment.data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
filename = attachment.getFilename()
|
||||||
|
except AttributeError:
|
||||||
|
filename = 'unknown.bin'
|
||||||
|
_logger.warning('Could not get filename for attachment, using default: %s', filename)
|
||||||
|
|
||||||
|
if hasattr(attachment, 'contentId') and attachment.contentId:
|
||||||
|
# Image avec CID - utiliser MIMEImage
|
||||||
|
maintype, subtype = (attachment.mimetype or 'image/jpeg').split('/', 1)
|
||||||
|
if maintype == 'image':
|
||||||
|
mime_part = MIMEImage(attachment.data, _subtype=subtype)
|
||||||
|
mime_part.add_header('Content-ID', f"<{attachment.contentId.strip('<>')}>")
|
||||||
|
mime_part.add_header('Content-Disposition', 'inline', filename=filename)
|
||||||
|
_logger.info('Adding inline image with CID: %s, filename: %s', attachment.contentId, filename)
|
||||||
|
else:
|
||||||
|
# Fallback pour les non-images avec CID
|
||||||
|
mime_part = MIMEBase(maintype, subtype)
|
||||||
|
mime_part.set_payload(attachment.data)
|
||||||
|
encoders.encode_base64(mime_part)
|
||||||
|
mime_part.add_header('Content-ID', f"<{attachment.contentId.strip('<>')}>")
|
||||||
|
mime_part.add_header('Content-Disposition', 'inline', filename=filename)
|
||||||
|
else:
|
||||||
|
# Pièce jointe normale
|
||||||
|
maintype, subtype = (attachment.mimetype or 'application/octet-stream').split('/', 1)
|
||||||
|
mime_part = MIMEBase(maintype, subtype)
|
||||||
|
mime_part.set_payload(attachment.data)
|
||||||
|
encoders.encode_base64(mime_part)
|
||||||
|
mime_part.add_header('Content-Disposition', 'attachment', filename=filename)
|
||||||
|
|
||||||
|
email_msg.attach(mime_part)
|
||||||
|
|
||||||
|
return email_msg.as_string()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error('Error converting MSG to EML: %s', str(e), exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
def process_msg_as_email(self):
|
def process_msg_as_email(self):
|
||||||
"""Convert MSG file to RFC822 email format and process it using Odoo's mail module."""
|
"""Convert MSG file to EML and process it using Odoo's mail module."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
_logger.info('Starting MSG processing for file: %s (model: %s, res_id: %s)',
|
_logger.info('Starting MSG processing for file: %s (model: %s, res_id: %s)',
|
||||||
self.name, self.res_model, self.res_id)
|
self.name, self.res_model, self.res_id)
|
||||||
|
|
@ -62,69 +162,9 @@ class IrAttachment(models.Model):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Read the MSG file
|
# Convert MSG to EML
|
||||||
_logger.info('Decoding MSG file data')
|
|
||||||
msg_data = base64.b64decode(self.datas)
|
msg_data = base64.b64decode(self.datas)
|
||||||
msg_file = extract_msg.Message(msg_data)
|
eml_content = self._msg_to_eml(msg_data)
|
||||||
_logger.info('MSG file info - Subject: %s, From: %s, To: %s',
|
|
||||||
msg_file.subject, msg_file.sender, msg_file.to)
|
|
||||||
|
|
||||||
# Convert MSG to email format
|
|
||||||
email_msg = EmailMessage()
|
|
||||||
|
|
||||||
# Add headers
|
|
||||||
email_msg['Subject'] = self._clean_header_value(msg_file.subject) or _("No Subject")
|
|
||||||
email_msg['From'] = self._clean_header_value(msg_file.sender)
|
|
||||||
email_msg['To'] = self._clean_header_value(msg_file.to)
|
|
||||||
email_msg['Cc'] = self._clean_header_value(msg_file.cc)
|
|
||||||
email_msg['Date'] = msg_file.date.strftime('%a, %d %b %Y %H:%M:%S %z') if msg_file.date else formatdate(localtime=True)
|
|
||||||
|
|
||||||
# Add original MSG headers if present
|
|
||||||
for header in ['Message-ID', 'In-Reply-To', 'References']:
|
|
||||||
if header.lower() in msg_file.header:
|
|
||||||
email_msg[header] = self._clean_header_value(msg_file.header[header.lower()])
|
|
||||||
|
|
||||||
# Set the body
|
|
||||||
if msg_file.body:
|
|
||||||
_logger.info('Processing message body')
|
|
||||||
body = msg_file.body
|
|
||||||
# Convert [cid:xxx] to proper HTML img tags if needed
|
|
||||||
cid_pattern = r'\[cid:([^\]]+)\]'
|
|
||||||
body = re.sub(cid_pattern, r'<img src="cid:\1">', body)
|
|
||||||
# Clean HTML if needed
|
|
||||||
if '<html' in body.lower():
|
|
||||||
body = html_sanitize(body)
|
|
||||||
email_msg.set_content(body, subtype='html')
|
|
||||||
else:
|
|
||||||
_logger.warning('No message body found')
|
|
||||||
email_msg.set_content('', subtype='html')
|
|
||||||
|
|
||||||
# Add attachments
|
|
||||||
_logger.info('Processing %d attachments', len(msg_file.attachments))
|
|
||||||
for attachment in msg_file.attachments:
|
|
||||||
if not hasattr(attachment, 'data') or not attachment.data:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
filename = attachment.getFilename()
|
|
||||||
except AttributeError:
|
|
||||||
filename = 'unknown.bin'
|
|
||||||
_logger.warning('Could not get filename for attachment, using default: %s', filename)
|
|
||||||
|
|
||||||
maintype, subtype = (attachment.mimetype or 'application/octet-stream').split('/', 1)
|
|
||||||
|
|
||||||
headers = {}
|
|
||||||
if hasattr(attachment, 'content_id') and attachment.content_id:
|
|
||||||
cid = attachment.content_id.strip('<>')
|
|
||||||
headers['Content-ID'] = f'<{cid}>'
|
|
||||||
|
|
||||||
email_msg.add_attachment(
|
|
||||||
attachment.data,
|
|
||||||
maintype=maintype,
|
|
||||||
subtype=subtype,
|
|
||||||
filename=filename,
|
|
||||||
headers=headers
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process using Odoo's standard mail handling
|
# Process using Odoo's standard mail handling
|
||||||
thread_model = self.env[self.res_model] if self.res_model else self.env['mail.thread']
|
thread_model = self.env[self.res_model] if self.res_model else self.env['mail.thread']
|
||||||
|
|
@ -141,10 +181,10 @@ class IrAttachment(models.Model):
|
||||||
'mail_thread_quote': False,
|
'mail_thread_quote': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Process the email using Odoo's standard message_process
|
# Process the EML using Odoo's standard message_process
|
||||||
thread_id = thread_model.with_context(**context).message_process(
|
thread_id = thread_model.with_context(**context).message_process(
|
||||||
model=self.res_model,
|
model=self.res_model,
|
||||||
message=email_msg.as_bytes(),
|
message=eml_content,
|
||||||
custom_values={
|
custom_values={
|
||||||
'res_id': self.res_id,
|
'res_id': self.res_id,
|
||||||
'model': self.res_model,
|
'model': self.res_model,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue