Created
September 12, 2018 13:40
-
-
Save MickeyPvX/186b1ed24a206004babd5b239c9182b8 to your computer and use it in GitHub Desktop.
Interact with MS Outlook: download attachments, create MailItems
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import pandas as pd | |
from comtypes.client import CreateObject, GetActiveObject | |
class OutlookInstance(): | |
"""Class to help with MS Outlook interoperability | |
Attributes: | |
outlook (comtypes.POINTER(_Application)): MS Outlook Application | |
namespace (comtypes.POINTER(_NameSpace)): Namespace for outlook; Default = 'MAPI' | |
mailbox (comtypes.POINTER()): Mailbox for target Outlook account | |
targetfolder (comtypes.POINTER()): Outlook folder in mailbox to search | |
dlfolder (str): Folder path to which to download attachments | |
reportdata (dict): Dictionary of pandas DataFrames containing data from selected attachments | |
dlerrors (list): List of errors that occurred during attachment downloading | |
delfolder (comtypes.POINTER()): Deleted Items folder for mailbox | |
Methods: | |
set_namespace: Sets the namespace for outlook object | |
set_mailbox: Sets the mailbox in namespace, if present. | |
set_target_folder: Sets the targetfolder in mailbox | |
download_attachment_data: downloads specified attachments to dlfolder, retains data to reportdata dictionary | |
create_mail: Create an email item to modify or send automatically | |
close: dispose of the OutlookInstance object; close Outlook Application | |
""" | |
class Oli(): | |
"""Iterator for objects in the Outlook object. Credit to 'Martin Stancik': | |
https://stackoverflow.com/questions/5077625/reading-e-mails-from-outlook-with-python-through-mapi | |
Attributes: | |
_obj (outlook_object): object from an outlook application that contains items | |
Methods: | |
items: Yields the items for _obj | |
prop: Gets properties for the items in _obj | |
""" | |
def __init__(self, outlook_object): | |
self._obj = outlook_object | |
def items(self): | |
array_size = self._obj.Count | |
for item_index in range(1, array_size + 1): | |
yield (item_index, self._obj[item_index]) | |
def prop(self): | |
return sorted(self._obj._prop_map_get_.keys()) | |
def __enter__(self): | |
return self | |
def __init__(self, mailbox, namespace='MAPI', tgtfolder=None): | |
"""Initializes the object | |
Args: | |
mailbox (str): Name of the mailbox to use | |
namespace (str): Namespace to use in Outlook; Default = 'MAPI' | |
tgtfolder (str): Name of the folder in mailbox to search; Default = None | |
""" | |
try: | |
self.outlook = GetActiveObject('Outlook.Application') | |
except: | |
self.outlook = CreateObject('Outlook.Application') | |
self.namespace = self.outlook.GetNamespace(namespace) | |
self.set_mailbox(mailbox) | |
if tgtfolder: | |
self.set_target_folder(tgtfolder) | |
self.dlfolder = os.path.abspath('.') | |
self.reportdata = {} | |
self.dlerrors = [] | |
print('Outlook Instance initialized.') | |
def set_namespace(self, namespace): | |
"""Sets the NameSpace for the Outlook Application | |
Args: | |
namespace (str): NameSpace to search for | |
""" | |
self.namespace = self.outlook.GetNamespace(namespace) | |
def set_mailbox(self, mailbox): | |
"""Sets the mailbox to use in the Outlook object | |
Args: | |
mailbox (str): Mailbox to search for | |
""" | |
found = False | |
for _, mbox in self.Oli(self.namespace.Folders).items(): | |
if mailbox.upper() in mbox.Name.upper(): | |
self.mailbox = mbox | |
found = True | |
break | |
if not found: | |
print('Mailbox: {} not found!'.format(mailbox)) | |
def set_target_folder(self, search_folder): | |
"""Sets the target folder in the mailbox | |
Args: | |
search_folder (str): Name of the folder to search for | |
Methods: | |
list_folders: recursively searches for the target folder, returns it when found | |
""" | |
def list_folders(olObject,target): | |
"""Recursive function to search all folders in an Outlook object for a target folder | |
Args: | |
olObject (comtypes.POINTER()): Object containing folders to be searched | |
target (str): Name of the folder to search for | |
Returns: | |
found_folder (comtypes.POINTER()): Folder with name matching target | |
""" | |
nonlocal found_folder | |
for _, folder in olObject.items(): | |
if found_folder is None: | |
if folder.Name.upper() == target.upper(): | |
found_folder = folder | |
elif 'Deleted Items' in folder.Name: | |
self.delfolder = folder | |
list_folders(self.Oli(folder.Folders), target) | |
else: | |
list_folders(self.Oli(folder.Folders), target) | |
else: | |
return found_folder | |
found_folder = None | |
list_folders(self.Oli(self.mailbox.Folders), search_folder) | |
if found_folder is None: | |
print('Folder {} not found in mailbox {}!'.format(search_folder, self.mailbox.Name)) | |
else: | |
self.targetfolder = found_folder | |
def download_attachment_data(self, subject_list, reports_list, dlfolder=None, filetypes='excel', | |
sheet_names=None, headers=0, del_emails=False, del_files=False): | |
"""Downloads attachments in reports_list from emails with subjects in subject_list to dlfolder, retains data from | |
attachments in report_data dictionary. | |
Args: | |
subject_list (list): Email subjects containing attachments to download | |
reports_list (list): Names of attachments to download | |
dlfolder (str): File folder where attachments are downloaded; Default = None | |
filetypes (str): Types of files to download; currently only supporting Excel attachments; Default = 'excel' | |
sheet_names (list): Names of worksheets in Excel to read; Default = None | |
headers (int): Location of data headers in the attachments; Default = 0 | |
del_emails (bool): Whether to delete the emails after downloading attachments; Default = False | |
del_files (bool): Whether to retain the downloaded files after extracting data from them; Default = False | |
""" | |
if not self.targetfolder: | |
print('Target Folder not set; run set_target_folder().') | |
return 0 | |
else: | |
self.dlerrors = [] | |
if not dlfolder: | |
dlfolder = self.dlfolder | |
if not os.path.isdir(dlfolder): | |
os.mkdir(dlfolder) | |
itemslist = list(self.Oli(self.targetfolder.Items).items()) | |
todelete = [] | |
for i in range(len(itemslist) - 1, -1, -1): | |
item = itemslist[i][1] | |
if item.Subject is None: | |
continue | |
if any(x.upper() in item.Subject.upper() for x in subject_list): | |
attachments = item.Attachments | |
for j in range(attachments.Count): | |
att = attachments.Item(j + 1) | |
if any(y.upper() in att.FileName.upper() for y in reports_list): | |
try: | |
fullpath = os.path.join(dlfolder, att.FileName) | |
if not os.path.isfile(fullpath): | |
att.SaveAsFile(fullpath) | |
print('{} Saved successfully'.format(att.FileName)) | |
else: | |
print('{} Already downloaded'.format(att.FileName)) | |
key, _ = att.FileName.split('.') | |
if filetypes == 'excel': | |
self.reportdata[key] = pd.read_excel(fullpath, | |
sheet_name=sheet_names, | |
header=headers) | |
else: | |
print('File Type loading not yet implemented.') | |
if del_files: | |
os.remove(fullpath) | |
except Exception as e: | |
self.dlerrors.append((att.FileName, e.args)) | |
continue | |
if del_emails: | |
todelete.append(item.Subject) | |
item.Delete() | |
delitemslist = list(self.Oli(self.delfolder.Items).items()) | |
for j in range(len(delitemslist) - 1, -1, -1): | |
if delitemslist[j][1].Subject in todelete: | |
delitemslist[j][1].Delete() | |
def create_mail(self, to=None, subject=None, body=None, html_body=None, att_path=None, auto_send=False): | |
"""Creates an Outlook MailItem | |
Args: | |
to (list/str): List of email addresses to add to the To: list | |
subject (str): Subject line of the email | |
body (str): Body of the email | |
html_body (str): Body of the email in HTML | |
att_path (str): Path to the file to attach | |
auto_send (bool): Whether to send the email automatically or display the item to be modified further | |
""" | |
olMailItem = 0x0 | |
item = self.outlook.CreateItem(olMailItem) | |
if isinstance(to, list): | |
str_to = '; '.join(to) | |
item.To = str_to | |
elif isinstance(to, str): | |
item.To = to | |
else: | |
raise TypeError('To: must be a string or list of strings') | |
item.Subject = subject | |
item.Body = body | |
if att_path: | |
item.Attachments.Add(att_path) | |
if auto_send: | |
# item.Display() | |
item.Send() | |
else: | |
item.Display() | |
def close(self): | |
self.namespace.Logoff() | |
self.outlook.Quit() | |
self.namespace = None | |
self.outlook = None | |
def __exit__(self, *args, **kwargs): | |
self.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment