Skip to content

Commit

Permalink
You can now save Messages and attached messages as EML files. Fix for g…
Browse files Browse the repository at this point in the history
  • Loading branch information
Alejandro Casanovas committed Sep 20, 2019
1 parent c31b258 commit ff50a30
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 4 deletions.
2 changes: 1 addition & 1 deletion O365/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def mailbox(self, resource=None):
:param str resource: Custom resource to be used in this mailbox
(Defaults to parent main_resource)
:return: a representation of account mailbox
:rtype: MailBox
:rtype: O365.mailbox.MailBox
"""
from .mailbox import MailBox
return MailBox(parent=self, main_resource=resource, name='MailBox')
Expand Down
82 changes: 79 additions & 3 deletions O365/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# noinspection PyPep8Naming
from bs4 import BeautifulSoup as bs
from dateutil.parser import parse
from pathlib import Path

from .utils import OutlookWellKnowFolderNames, ApiComponent, \
BaseAttachments, BaseAttachment, AttachableMixin, ImportanceLevel, \
Expand All @@ -31,17 +32,55 @@ class Flag(CaseEnum):
class MessageAttachment(BaseAttachment):
_endpoints = {
'attach': '/messages/{id}/attachments',
'attachment': '/messages/{id}/attachments/{ida}'
'attachment': '/messages/{id}/attachments/{ida}',
}


class MessageAttachments(BaseAttachments):
_endpoints = {
'attachments': '/messages/{id}/attachments',
'attachment': '/messages/{id}/attachments/{ida}'
'attachment': '/messages/{id}/attachments/{ida}',
'get_mime': '/messages/{id}/attachments/{ida}/$value',
}
_attachment_constructor = MessageAttachment

def save_as_eml(self, attachment, to_path=None):
""" Saves this message as and EML to the file system
:param MessageAttachment attachment: the MessageAttachment to store as eml.
:param Path or str to_path: the path where to store this file
"""
if not attachment or not isinstance(attachment, MessageAttachment) \
or attachment.attachment_id is None or attachment.attachment_type != 'item':
raise ValueError('Must provide a saved "item" attachment of type MessageAttachment')

if to_path is None:
to_path = Path()
else:
if not isinstance(to_path, Path):
to_path = Path(to_path)

if not to_path.suffix:
to_path = to_path.with_suffix('.eml')

msg_id = self._parent.object_id
if msg_id is None:
raise RuntimeError('Attempting to get the mime contents of an unsaved message')

url = self.build_url(self._endpoints.get('get_mime').format(id=msg_id, ida=attachment.attachment_id))

response = self._parent.con.get(url)

if not response:
return False

mime_content = response.content

if mime_content:
with to_path.open('wb') as file_obj:
file_obj.write(mime_content)
return True
return False


class MessageFlag(ApiComponent):
""" A flag on a message """
Expand Down Expand Up @@ -172,7 +211,8 @@ class Message(ApiComponent, AttachableMixin, HandleRecipientsMixin):
'copy_message': '/messages/{id}/copy',
'create_reply': '/messages/{id}/createReply',
'create_reply_all': '/messages/{id}/createReplyAll',
'forward_message': '/messages/{id}/createForward'
'forward_message': '/messages/{id}/createForward',
'get_mime': '/messages/{id}/$value',
}

def __init__(self, *, parent=None, con=None, **kwargs):
Expand Down Expand Up @@ -961,3 +1001,39 @@ def get_event(self):
event_data = data.get(self._cc('event'))

return Event(parent=self, **{self._cloud_data_key: event_data})

def get_mime_content(self):
""" Returns the MIME contents of this message """
if self.object_id is None:
raise RuntimeError('Attempting to get the mime contents of an unsaved message')

url = self.build_url(self._endpoints.get('get_mime').format(id=self.object_id))

response = self.con.get(url)

if not response:
return None

return response.content

def save_as_eml(self, to_path=None):
""" Saves this message as and EML to the file system
:param Path or str to_path: the path where to store this file
"""

if to_path is None:
to_path = Path()
else:
if not isinstance(to_path, Path):
to_path = Path(to_path)

if not to_path.suffix:
to_path = to_path.with_suffix('.eml')

mime_content = self.get_mime_content()

if mime_content:
with to_path.open('wb') as file_obj:
file_obj.write(mime_content)
return True
return False
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,22 @@ print(msg.message_headers) # returns a list of dicts.

Note that only message headers and other properties added to the select statement will be present.

##### Saving as EML
Messages and attached messages can be saved as *.eml.

- Save message as "eml":
```python
msg.save_as_eml(to_path=Path('my_saved_email.eml'))
```
- Save attached message as "eml":

Carefull: there's no way to identify that an attachment is in fact a message. You can only check if the attachment.attachment_type == 'item'.
if is of type "item" then it can be a message (or an event, etc...). You will have to determine this yourself.

```python
msg_attachment = msg.attachments[0] # the first attachment is attachment.attachment_type == 'item' and I know it's a message.
mg.attachments.save_as_eml(msg_attachment, to_path=Path('my_saved_email.eml'))
```

## AddressBook
AddressBook groups the funcionality of both the Contact Folders and Contacts. Outlook Distribution Groups are not supported (By the Microsoft API's).
Expand Down

0 comments on commit ff50a30

Please sign in to comment.