如何使用ItemAdd Event监视Python中的共享收件箱?

时间:2018-05-06 00:49:56

标签: python outlook win32com pythoncom

我想在Python中制作一个Outlook监视器,并一直在研究这个主题。

我正在使用Python 3.6尝试监控我的Outlook上共享收件箱中收到的电子邮件。我有以下代码将运行:

import win32com.client
import ctypes # for the VM_QUIT to stop PumpMessage()
import pythoncom
import re
import time
import psutil
import os

from os.path import join as join_path
from olefile import OleFileIO


class HandlerClass():
    def __init__(self):
        # First action to do when using the class in the DispatchWithEvents
        messages = self  # Getting All Messages in the Inbox Folder
        #headers_schema = "http://schemas.microsoft.com/mapi/proptag/0x007D001E"

        # Check for unread emails when starting the event
        for message in messages:
            if message.UnRead:
                self.check_email(message)
            else:
                print("No Unread Emails")
                break

    def check_email(self, message):
        attachments = message.Attachments  # Get attachments
        if attachments.Count > 0:  # If one or more attachments
            attachment = attachments.Item(1)  # Get the first attachment
            attachment_fname = join_path(os.getcwd(), attachment.FileName)
            attachment.SaveAsFile(attachment_fname)  # Saves to the attachment to current folded
            try:
                IHDict = extract_headers(attachment_fname)
            except MessageHeadersError as err:
                print("Could not extract headers. Is this an email?", err)
                return
            finally:
                os.remove(attachment_fname)  # Delete saved attachment

            def check_header(h: str) -> bool:
                hval = IHDict.get(h, None)
                if hval is not None:
                    print("%s: %s" % (h, hval))
                else:
                    print("'%s' not available." % h)
                return hval

            # Pull out headers of interest
            rp = check_header('Return-Path')
            sp = check_header('X-Env-Sender')
            check_header('X-Originating-Ip')
            check_header('Authentication-Results')
            print()  # Formatting

            # Compare Return Path to X-Env-Sender to check for Phish.
            if rp is not None and sp is not None and rp.strip("<>") == sp:
                print("Email is safe.")
                print() # Formatitng
            else:
                print("Email is suspicious.")
                print() # Formatting
        else:
            print("Email Contains No Attachment Message")

    def OnQuit(self):
        # To stop PumpMessages() when Outlook Quit
        # Note: Not sure it works when disconnecting!!
        ctypes.windll.user32.PostQuitMessage(0)

    def OnItemAdd(self, mail):
        print("Working!")
        #self.check_email(mail)
        #Check if the item is of the MailItem type
        if mail.Class==43:
            print(mail.Subject, " - ", mail.Parent.FolderPath)



# Function to check if outlook is open
def check_outlook_open():
    for pid in psutil.pids():
        p = psutil.Process(pid)
        if p.name().lower().strip() == 'outlook.exe':
            return True
    return False


class MessageHeadersError(Exception):
    pass


class MessageHeadersMissing(MessageHeadersError):
    pass


class MessageHeadersUnknownEncoding(MessageHeadersError):
    pass


def extract_headers(cdfv2_filename: str) -> dict:
    """Extract headers from a CDFv2 email"""
    try:
        ole = OleFileIO(cdfv2_filename)
    except Exception as exc:
        raise MessageHeadersError("could not open OLE file") from exc
    try:
        for ent in ole.listdir(streams=True, storages=False):
            if ent[-1].startswith("__substg1.0_007D"):
                # message headers 
                break
        else:
            # no message header entry?
            raise MessageHeadersMissing("missing")

        olestream = ole.openstream(ent)
        header_data = olestream.read()
        olestream.close()

        if ent[-1].endswith("001E"):
            # ASCII encoding
            header_string = header_data.decode("ascii")
        elif ent[-1].endswith("001F"):
            # UTF-16
            header_string = header_data.decode("UTF-16")
        else:
            # dunno what this encoding is
            raise MessageHeadersUnknownEncoding("Unknown OLE encoding " + ent[-4:])

        return parse_headers(header_string)
    finally:
        ole.close()


def parse_headers(hstr: str) -> dict:
    headers = {}
    lastkey = None

    for line in hstr.split("\n"):
        #if line.strip() == " ": #skip empty lines - there shouldn't be any. continue 
        if line.startswith("\t") or line.startswith(" "): #headers can be continued with either whitespace or tabs.
            key = lastkey
            value = line.strip()
        else:
            key, _, value = line.partition(":")# e - mail headers are case insensitive, #so we normalise to title - case (the most common form).
            key = key.strip().title()
            value = value.strip()
            lastkey = headers[key] = (headers.get(key, "") + " " + value).strip()
    return headers


# Loop 
Syscoms = win32com.client.DispatchEx("Outlook.Application").GetNamespace("MAPI").Folders["UK"].Folders["Inbox"].Items
while True:
    # If outlook opened then it will start the DispatchWithEvents
    if check_outlook_open():
        win32com.client.DispatchWithEvents(Syscoms, HandlerClass)
        pythoncom.PumpMessages()
    # To not check all the time (should increase 10 depending on your needs)
    time.sleep(1)

如何在新邮件到达收件箱时触发OnItemAdd事件?

1 个答案:

答案 0 :(得分:0)

尝试使用Dispatch代替DispatchEx,并使用GetSharedDefaultFolder代替Inbox。以下代码将收到的电子邮件正确注册到共享收件箱:

import ctypes # for the VM_QUIT to stop PumpMessage()
import pythoncom
import win32com.client
import sys


# outlook config
SHARED_MAILBOX = "Your Mailbox Name"

# get the outlook instance and inbox folder
session = win32com.client.Dispatch("Outlook.Application").Session
user = session.CreateRecipient(SHARED_MAILBOX)
shared_inbox = session.GetSharedDefaultFolder(user, 6).Items  # 6 is Inbox


class HandlerClass(object):

    def OnItemAdd(self, item):
        print("New item added in shared mailbox")
        if item.Class == 43:
            print("The item is an email!")


outlook = win32com.client.DispatchWithEvents(shared_inbox, HandlerClass)


def main():
    print("Starting up Outlook watcher")
    pythoncom.PumpMessages()


if __name__ == "__main__":
    try:
        status = main()
        sys.exit(status)
    except KeyboardInterrupt:
        print("Terminating program..")
        ctypes.windll.user32.PostQuitMessage(0)
        sys.exit()

编辑:

上面的代码中有一个错误,尽管这不是问题的一部分,但我不想这样。程序不会像这样捕获KeyboardInterrupt,因为PumpMessages是一个无限循环,会阻塞主线程。使用threading模块将其放入自己的线程中可以解决此问题:

class OtlkThread(threading.Thread):
    def run(self):
        logger.info("Starting up Outlook watcher\n"
                    "To terminate the program, press 'Ctrl + C'")
        pythoncom.PumpMessages()


def main():
    win32com.client.DispatchWithEvents(shared_inbox, HandlerClass)
    OtlkThread(daemon=True).start()


if __name__ == "__main__":
    status = main()
    while True:
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            logger.info("Terminating program..")
            ctypes.windll.user32.PostQuitMessage(0)
            sys.exit(status)