more msg for nesting
This commit is contained in:
parent
5aa0f5b519
commit
853c239deb
3 changed files with 284 additions and 133 deletions
|
|
@ -6,28 +6,28 @@ attachments, handling headers, and creating proper mail.message records.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from odoo import models, api, fields
|
from odoo import models, api, fields
|
||||||
from odoo.tools import html_sanitize
|
from odoo.tools import html_sanitize, config
|
||||||
import base64
|
|
||||||
import logging
|
|
||||||
from odoo.tools.translate import _
|
from odoo.tools.translate import _
|
||||||
|
from odoo.tools import email_normalize, email_split
|
||||||
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.image import MIMEImage
|
from email.mime.image import MIMEImage
|
||||||
from email.mime.base import MIMEBase
|
from email.mime.base import MIMEBase
|
||||||
from email import encoders
|
from email import encoders
|
||||||
import re
|
from email.utils import make_msgid, formatdate
|
||||||
from odoo.tools import email_normalize, email_split
|
from email.header import Header
|
||||||
from io import BytesIO
|
|
||||||
import extract_msg
|
import base64
|
||||||
import psycopg2
|
import logging
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from odoo.tools import config
|
import mimetypes
|
||||||
import time
|
import psycopg2
|
||||||
import random
|
import extract_msg
|
||||||
import socket
|
from io import BytesIO
|
||||||
import tempfile
|
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -253,10 +253,7 @@ class IrAttachment(models.Model):
|
||||||
msg_file.to,
|
msg_file.to,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create email message
|
# Clean and normalize email addresses for partner matching
|
||||||
email_msg = MIMEMultipart("related")
|
|
||||||
|
|
||||||
# Clean and normalize email addresses
|
|
||||||
from_email = email_normalize(msg_file.sender) if msg_file.sender else False
|
from_email = email_normalize(msg_file.sender) if msg_file.sender else False
|
||||||
to_emails = email_split(msg_file.to) if msg_file.to else []
|
to_emails = email_split(msg_file.to) if msg_file.to else []
|
||||||
cc_emails = email_split(msg_file.cc) if msg_file.cc else []
|
cc_emails = email_split(msg_file.cc) if msg_file.cc else []
|
||||||
|
|
@ -271,7 +268,7 @@ class IrAttachment(models.Model):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.warning("Failed to get current partner: %s", str(e))
|
_logger.warning("Failed to get current partner: %s", str(e))
|
||||||
|
|
||||||
# Recherche du partenaire expéditeur avec gestion d'erreur
|
# Find the sender partner with error handling
|
||||||
from_partner = None
|
from_partner = None
|
||||||
if from_email:
|
if from_email:
|
||||||
try:
|
try:
|
||||||
|
|
@ -293,20 +290,80 @@ class IrAttachment(models.Model):
|
||||||
from_partner = company_contacts[0]
|
from_partner = company_contacts[0]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.warning("Failed to process sender partner: %s", str(e))
|
_logger.warning("Failed to process sender partner: %s", str(e))
|
||||||
|
|
||||||
|
# Create email message
|
||||||
|
email_msg = MIMEMultipart('mixed')
|
||||||
|
|
||||||
|
# Set basic headers with proper encoding
|
||||||
|
email_msg['Subject'] = self._encode_header_content(msg_file.subject)
|
||||||
|
email_msg['From'] = self._encode_header_content(msg_file.sender)
|
||||||
|
email_msg['To'] = self._encode_header_content(msg_file.to)
|
||||||
|
|
||||||
|
# Format the date properly for email header
|
||||||
|
if msg_file.date:
|
||||||
|
if isinstance(msg_file.date, datetime.datetime):
|
||||||
|
timestamp = msg_file.date.timestamp()
|
||||||
|
else:
|
||||||
|
timestamp = time.time()
|
||||||
|
email_msg['Date'] = formatdate(timestamp, localtime=True)
|
||||||
|
else:
|
||||||
|
email_msg['Date'] = formatdate(time.time(), localtime=True)
|
||||||
|
|
||||||
|
# Generate a new Message-ID if not present
|
||||||
|
try:
|
||||||
|
message_id = msg_file.message_id
|
||||||
|
except AttributeError:
|
||||||
|
message_id = make_msgid()
|
||||||
|
email_msg['Message-ID'] = message_id
|
||||||
|
|
||||||
# Set email headers
|
# Handle CC if present
|
||||||
email_msg["Subject"] = self._clean_header_value(msg_file.subject)
|
|
||||||
email_msg["From"] = self._clean_header_value(msg_file.sender)
|
|
||||||
email_msg["To"] = self._clean_header_value(msg_file.to)
|
|
||||||
if msg_file.cc:
|
if msg_file.cc:
|
||||||
email_msg["Cc"] = self._clean_header_value(msg_file.cc)
|
email_msg['CC'] = self._encode_header_content(msg_file.cc)
|
||||||
|
|
||||||
# Add email body
|
# Create the message body
|
||||||
|
body_part = MIMEMultipart('alternative')
|
||||||
|
|
||||||
|
# Add HTML version if available
|
||||||
|
if msg_file.htmlBody:
|
||||||
|
_logger.info("Adding HTML body")
|
||||||
|
# Detect encoding and decode properly
|
||||||
|
html_content = msg_file.htmlBody
|
||||||
|
if isinstance(html_content, bytes):
|
||||||
|
encoding = self._detect_encoding(html_content)
|
||||||
|
try:
|
||||||
|
html_content = html_content.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
html_content = html_content.decode('utf-8', errors='replace')
|
||||||
|
html_part = MIMEText(html_content, 'html', 'utf-8')
|
||||||
|
body_part.attach(html_part)
|
||||||
|
# Add RTF version if available and no HTML
|
||||||
|
elif msg_file.rtfBody:
|
||||||
|
_logger.info("Adding RTF body")
|
||||||
|
rtf_content = msg_file.rtfBody
|
||||||
|
if isinstance(rtf_content, bytes):
|
||||||
|
encoding = self._detect_encoding(rtf_content)
|
||||||
|
try:
|
||||||
|
rtf_content = rtf_content.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
rtf_content = rtf_content.decode('utf-8', errors='replace')
|
||||||
|
rtf_part = MIMEText(rtf_content, 'rtf', 'utf-8')
|
||||||
|
body_part.attach(rtf_part)
|
||||||
|
# Fallback to plain text
|
||||||
if msg_file.body:
|
if msg_file.body:
|
||||||
body = html_sanitize(msg_file.body)
|
_logger.info("Adding plain text body")
|
||||||
email_msg.attach(MIMEText(body, "html"))
|
text_content = msg_file.body
|
||||||
|
if isinstance(text_content, bytes):
|
||||||
|
encoding = self._detect_encoding(text_content)
|
||||||
|
try:
|
||||||
|
text_content = text_content.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
text_content = text_content.decode('utf-8', errors='replace')
|
||||||
|
text_part = MIMEText(text_content, 'plain', 'utf-8')
|
||||||
|
body_part.attach(text_part)
|
||||||
|
|
||||||
|
email_msg.attach(body_part)
|
||||||
|
|
||||||
# Process attachments
|
# Process attachments with special handling for inline images
|
||||||
for att in msg_file.attachments:
|
for att in msg_file.attachments:
|
||||||
try:
|
try:
|
||||||
filename = att.longFilename or att.shortFilename
|
filename = att.longFilename or att.shortFilename
|
||||||
|
|
@ -315,118 +372,144 @@ class IrAttachment(models.Model):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
_logger.info("=== Processing attachment: %s ===", filename)
|
_logger.info("=== Processing attachment: %s ===", filename)
|
||||||
_logger.info("Attachment type: %s", type(att.data).__name__)
|
|
||||||
_logger.info("Attachment size: %d bytes", len(att.data) if hasattr(att.data, '__len__') else -1)
|
|
||||||
|
|
||||||
# Check if attachment is an email file (MSG or EML)
|
|
||||||
if isinstance(att.data, extract_msg.msg_classes.message.Message):
|
|
||||||
_logger.info("Processing as MSG file: %s", filename)
|
|
||||||
_logger.info("MSG attributes: %s", dir(att.data))
|
|
||||||
mime_type = "application/vnd.ms-outlook"
|
|
||||||
elif filename.lower().endswith('.eml'):
|
|
||||||
_logger.info("Processing as EML file: %s", filename)
|
|
||||||
mime_type = "message/rfc822"
|
|
||||||
else:
|
|
||||||
# Regular attachment
|
|
||||||
_logger.info("Processing as regular attachment: %s", filename)
|
|
||||||
_logger.info("Content type: %s", getattr(att, 'content_type', 'unknown'))
|
|
||||||
part = MIMEBase("application", "octet-stream")
|
|
||||||
part.set_payload(att.data)
|
|
||||||
encoders.encode_base64(part)
|
|
||||||
part.add_header(
|
|
||||||
"Content-Disposition",
|
|
||||||
f"attachment; filename={filename}",
|
|
||||||
)
|
|
||||||
email_msg.attach(part)
|
|
||||||
_logger.info("Regular attachment processed successfully")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Process email file (MSG or EML)
|
|
||||||
_logger.info("Creating MIME part with type: %s", mime_type)
|
|
||||||
maintype, subtype = mime_type.split('/')
|
|
||||||
part = MIMEBase(maintype, subtype)
|
|
||||||
|
|
||||||
# Get the raw data
|
# Check if it's an inline image
|
||||||
if isinstance(att.data, extract_msg.msg_classes.message.Message):
|
if hasattr(att, 'cid') and att.cid:
|
||||||
_logger.info("Extracting raw bytes from MSG")
|
# This is an inline image
|
||||||
|
_logger.info("Processing inline image with CID: %s", att.cid)
|
||||||
try:
|
try:
|
||||||
# Try to get raw data using asEmailMessage
|
# Detect MIME type based on filename
|
||||||
_logger.info("Converting MSG to email message")
|
mime_type, _ = mimetypes.guess_type(filename)
|
||||||
email_message = att.data.asEmailMessage()
|
if mime_type and mime_type.startswith('image/'):
|
||||||
raw_data = email_message.as_bytes()
|
image_part = MIMEImage(att.data, _subtype=mime_type.split('/')[-1])
|
||||||
_logger.info("Successfully converted MSG to email message: %d bytes", len(raw_data))
|
else:
|
||||||
|
# Fallback to application/octet-stream for unknown types
|
||||||
|
part = MIMEBase("application", "octet-stream")
|
||||||
|
part.set_payload(att.data)
|
||||||
|
encoders.encode_base64(part)
|
||||||
|
part.add_header('Content-ID', f'<{att.cid}>')
|
||||||
|
part.add_header('Content-Disposition', 'inline', filename=filename)
|
||||||
|
email_msg.attach(part)
|
||||||
|
continue
|
||||||
|
|
||||||
|
image_part.add_header('Content-ID', f'<{att.cid}>')
|
||||||
|
image_part.add_header('Content-Disposition', 'inline', filename=filename)
|
||||||
|
email_msg.attach(image_part)
|
||||||
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.warning("Failed to convert MSG to email message: %s", str(e))
|
_logger.warning("Failed to process inline image: %s", str(e))
|
||||||
try:
|
|
||||||
# Try to export as bytes
|
|
||||||
_logger.info("Trying to export MSG as bytes")
|
|
||||||
raw_data = att.data.exportBytes()
|
|
||||||
_logger.info("Successfully exported MSG as bytes: %d bytes", len(raw_data))
|
|
||||||
except Exception as e:
|
|
||||||
_logger.warning("Failed to export MSG as bytes: %s", str(e))
|
|
||||||
# Last resort: try to get the raw data directly
|
|
||||||
if hasattr(att.data, '_raw'):
|
|
||||||
_logger.info("Using _raw attribute")
|
|
||||||
raw_data = att.data._raw
|
|
||||||
else:
|
|
||||||
_logger.warning("Could not get raw data from MSG, skipping attachment")
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
_logger.info("Using raw data directly for EML")
|
|
||||||
raw_data = att.data
|
|
||||||
|
|
||||||
if raw_data:
|
# Regular attachment processing
|
||||||
_logger.info("Raw data size: %d bytes", len(raw_data) if raw_data else 0)
|
_logger.info("Processing as regular attachment: %s", filename)
|
||||||
|
part = MIMEBase("application", "octet-stream")
|
||||||
# Check if the data is base64 encoded
|
part.set_payload(att.data)
|
||||||
try:
|
encoders.encode_base64(part)
|
||||||
# Try to decode base64 if it looks like base64
|
part.add_header(
|
||||||
if isinstance(raw_data, str) and all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' for c in raw_data):
|
"Content-Disposition",
|
||||||
_logger.info("Data appears to be base64 encoded, decoding...")
|
f"attachment; filename={filename}",
|
||||||
import base64
|
|
||||||
raw_data = base64.b64decode(raw_data)
|
|
||||||
_logger.info("Successfully decoded base64 data: %d bytes", len(raw_data))
|
|
||||||
except Exception as e:
|
|
||||||
_logger.warning("Failed to decode base64 data: %s", str(e))
|
|
||||||
|
|
||||||
# Set the payload and encode
|
|
||||||
part.set_payload(raw_data)
|
|
||||||
_logger.info("Encoding attachment in base64")
|
|
||||||
encoders.encode_base64(part)
|
|
||||||
|
|
||||||
# Add headers
|
|
||||||
_logger.info("Adding headers to MIME part")
|
|
||||||
part.add_header(
|
|
||||||
"Content-Disposition",
|
|
||||||
f"attachment; filename={filename}",
|
|
||||||
)
|
|
||||||
part.add_header(
|
|
||||||
"Content-Type",
|
|
||||||
mime_type,
|
|
||||||
name=filename
|
|
||||||
)
|
|
||||||
|
|
||||||
_logger.info("Attaching processed email file to message")
|
|
||||||
email_msg.attach(part)
|
|
||||||
_logger.info("=== Finished processing %s ===", filename)
|
|
||||||
else:
|
|
||||||
_logger.warning("No raw data available for %s, skipping", filename)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
_logger.warning(
|
|
||||||
"Failed to process attachment %s: %s - Attachment data type: %s, Content: %s",
|
|
||||||
filename,
|
|
||||||
str(e),
|
|
||||||
type(att.data),
|
|
||||||
str(att.data)[:100] if att.data else "None",
|
|
||||||
exc_info=True
|
|
||||||
)
|
)
|
||||||
return email_msg.as_string()
|
email_msg.attach(part)
|
||||||
|
_logger.info("Regular attachment processed successfully")
|
||||||
|
except Exception as e:
|
||||||
|
_logger.warning("Failed to process attachment %s: %s",
|
||||||
|
filename if 'filename' in locals() else 'Unknown',
|
||||||
|
str(e))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Prepare custom values for message processing
|
||||||
|
custom_values = {}
|
||||||
|
if from_partner:
|
||||||
|
custom_values['author_id'] = from_partner.id
|
||||||
|
|
||||||
|
# If this is related to a sale, add the sale thread
|
||||||
|
if self.res_model == 'sale.order' and self.res_id:
|
||||||
|
custom_values['model'] = 'sale.order'
|
||||||
|
custom_values['res_id'] = self.res_id
|
||||||
|
|
||||||
|
# Return the complete email message as string
|
||||||
|
try:
|
||||||
|
eml_content = email_msg.as_string()
|
||||||
|
|
||||||
|
# Determine the target model and thread_id
|
||||||
|
target_model = self.res_model or 'mail.thread'
|
||||||
|
target_thread_id = self.res_id if self.res_model else False
|
||||||
|
|
||||||
|
# Process the email with forced model
|
||||||
|
self.env['mail.thread'].with_context(
|
||||||
|
mail_create_nosubscribe=True, # Don't auto-subscribe the sender
|
||||||
|
mail_create_nolog=True, # Don't create log message
|
||||||
|
default_model=target_model, # Force the model
|
||||||
|
).message_process(
|
||||||
|
model=target_model,
|
||||||
|
message=eml_content,
|
||||||
|
custom_values=custom_values,
|
||||||
|
save_original=True,
|
||||||
|
strip_attachments=False,
|
||||||
|
thread_id=target_thread_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return the EML content instead of True
|
||||||
|
return eml_content
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error("Error converting email message to string: %s", str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error("Error converting MSG to EML: %s", str(e))
|
_logger.error("Error converting MSG to EML: %s", str(e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def _encode_header_content(self, content):
|
||||||
|
"""
|
||||||
|
Encode header content properly to handle special characters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content (str): Content to encode
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Encoded content
|
||||||
|
"""
|
||||||
|
if not content:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if isinstance(content, bytes):
|
||||||
|
encoding = self._detect_encoding(content)
|
||||||
|
try:
|
||||||
|
content = content.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
content = content.decode('utf-8', errors='replace')
|
||||||
|
|
||||||
|
from email.header import Header
|
||||||
|
return str(Header(content, 'utf-8'))
|
||||||
|
|
||||||
|
def _ensure_html_content(self, content, is_html=False):
|
||||||
|
"""
|
||||||
|
Ensure content is in HTML format and properly formatted.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content (str): The content to format
|
||||||
|
is_html (bool): Whether the content is already HTML
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: HTML formatted content
|
||||||
|
"""
|
||||||
|
if not content:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# If content is already HTML, just sanitize it
|
||||||
|
if is_html or bool(re.search(r'<[^>]+>', content)):
|
||||||
|
return html_sanitize(content)
|
||||||
|
|
||||||
|
# Convert plain text to HTML
|
||||||
|
content = content.replace('&', '&')
|
||||||
|
content = content.replace('<', '<')
|
||||||
|
content = content.replace('>', '>')
|
||||||
|
content = content.replace('\n', '<br/>')
|
||||||
|
|
||||||
|
# Wrap in HTML tags
|
||||||
|
html_content = f"<div style='font-family: Arial, sans-serif;'>{content}</div>"
|
||||||
|
return html_sanitize(html_content)
|
||||||
|
|
||||||
def _get_review_dir(self, error_type):
|
def _get_review_dir(self, error_type):
|
||||||
"""
|
"""
|
||||||
Get the review directory path.
|
Get the review directory path.
|
||||||
|
|
@ -499,7 +582,7 @@ class IrAttachment(models.Model):
|
||||||
Convert MSG file to EML and process it using Odoo's mail module.
|
Convert MSG file to EML and process it using Odoo's mail module.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if successful, False otherwise
|
str: EML content if successful, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|
@ -583,12 +666,12 @@ class IrAttachment(models.Model):
|
||||||
"msg_processed": True,
|
"msg_processed": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return thread_id
|
return eml_content
|
||||||
|
|
||||||
except psycopg2.IntegrityError as e:
|
except psycopg2.IntegrityError as e:
|
||||||
if "mail_followers_mail_followers_res_partner_res_model_id_uniq" in str(e):
|
if "mail_followers_mail_followers_res_partner_res_model_id_uniq" in str(e):
|
||||||
_logger.warning("Follower already exists, skipping: %s", str(e))
|
_logger.warning("Follower already exists, skipping: %s", str(e))
|
||||||
return thread_id if 'thread_id' in locals() else False
|
return eml_content if 'eml_content' in locals() else False
|
||||||
raise
|
raise
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
BIN
msg_viewer/test/FACTURE NO. 7005451.msg
Normal file
BIN
msg_viewer/test/FACTURE NO. 7005451.msg
Normal file
Binary file not shown.
68
msg_viewer/test/analyze_msg.py
Normal file
68
msg_viewer/test/analyze_msg.py
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import extract_msg
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def analyze_msg(file_path):
|
||||||
|
print(f"Analyzing MSG file: {file_path}")
|
||||||
|
msg = extract_msg.Message(file_path)
|
||||||
|
|
||||||
|
print("\n=== Basic Information ===")
|
||||||
|
print(f"Subject: {msg.subject}")
|
||||||
|
print(f"From: {msg.sender}")
|
||||||
|
print(f"To: {msg.to}")
|
||||||
|
|
||||||
|
print("\n=== Body Properties ===")
|
||||||
|
print("Has RTF:", hasattr(msg, '_rtf'))
|
||||||
|
|
||||||
|
print("\nPlain Body Type:", type(msg.body))
|
||||||
|
print("Plain Body Preview:")
|
||||||
|
print("---START OF PLAIN BODY---")
|
||||||
|
print(msg.body[:500])
|
||||||
|
print("---END OF PLAIN BODY---")
|
||||||
|
|
||||||
|
print("\nHTML Body Preview:")
|
||||||
|
print("---START OF HTML BODY---")
|
||||||
|
if msg.htmlBody:
|
||||||
|
try:
|
||||||
|
html_content = msg.htmlBody.decode('utf-8', errors='ignore')
|
||||||
|
print(html_content[:1000])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error decoding HTML body: {e}")
|
||||||
|
else:
|
||||||
|
print("No HTML body found")
|
||||||
|
print("---END OF HTML BODY---")
|
||||||
|
|
||||||
|
print("\nRTF Body Preview:")
|
||||||
|
print("---START OF RTF BODY---")
|
||||||
|
if msg.rtfBody:
|
||||||
|
try:
|
||||||
|
rtf_content = msg.rtfBody.decode('utf-8', errors='ignore')
|
||||||
|
print(rtf_content[:1000])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error decoding RTF body: {e}")
|
||||||
|
else:
|
||||||
|
print("No RTF body found")
|
||||||
|
print("---END OF RTF BODY---")
|
||||||
|
|
||||||
|
print("\n=== Images ===")
|
||||||
|
for att in msg.attachments:
|
||||||
|
if att.longFilename and 'image' in att.longFilename.lower():
|
||||||
|
print(f"Found image: {att.longFilename}")
|
||||||
|
print(f"Content-ID: {att.cid if hasattr(att, 'cid') else 'No CID'}")
|
||||||
|
|
||||||
|
print("\n=== Available Attributes ===")
|
||||||
|
for attr in dir(msg):
|
||||||
|
if not attr.startswith('_'):
|
||||||
|
try:
|
||||||
|
value = getattr(msg, attr)
|
||||||
|
if not callable(value):
|
||||||
|
print(f"{attr}: {type(value)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{attr}: Error accessing - {str(e)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
file_path = "FACTURE NO. 7005451.msg"
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
analyze_msg(file_path)
|
||||||
|
else:
|
||||||
|
print(f"File not found: {file_path}")
|
||||||
Loading…
Reference in a new issue