📄 msgstore.py
字号:
# Problem #2: Outlook decodes quoted-printable and base64 on its # own, but leaves any Content-Transfer-Encoding line in the headers. # This can cause the email pkg to try to decode the text again, # with unpleasant (but rarely fatal) results. If we strip that # header too, no problem -- although the fact that a msg was # encoded in base64 is usually a good spam clue, and we miss that. # # Short course: we either have to synthesize non-insane MIME # structure, or eliminate all evidence of original MIME structure. # Since we don't have a way to the former, by default this function # does the latter. import email text = self._GetMessageText() try: try: msg = email.message_from_string(text) except email.Errors.BoundaryError: # In case this is the # "No terminating boundary and no trailing empty line" # flavor of BoundaryError, we can supply a trailing empty # line to shut it up. It's certainly ill-formed MIME, and # probably spam. We don't care about the exact MIME # structure, just the words it contains, so no harm and # much good in trying to suppress this error. try: msg = email.message_from_string(text + "\n\n") except email.Errors.BoundaryError: msg = None except email.Errors.HeaderParseError: # This exception can come from parsing the header *or* the # body of a mime message. msg = None # But even this doesn't get *everything*. We can still see: # "multipart message with no defined boundary" or the # HeaderParseError above. Time to get brutal - hack out # the Content-Type header, so we see it as plain text. if msg is None: butcher_pos = text.lower().find("\ncontent-type: ") if butcher_pos < 0: # This error just just gunna get caught below anyway raise RuntimeError( "email package croaked with a MIME related error, but " "there appears to be no 'Content-Type' header") # Put it back together, skipping the original "\n" but # leaving the header leaving "\nSpamBayes-Content-Type: " butchered = text[:butcher_pos] + "\nSpamBayes-" + \ text[butcher_pos+1:] + "\n\n" msg = email.message_from_string(butchered) except: print "FAILED to create email.message from: ", `text` raise if strip_mime_headers: if msg.has_key('content-type'): del msg['content-type'] if msg.has_key('content-transfer-encoding'): del msg['content-transfer-encoding'] return msg def SetField(self, prop, val): # Future optimization note - from GetIDsFromNames doco # Name-to-identifier mapping is represented by an object's # PR_MAPPING_SIGNATURE property. PR_MAPPING_SIGNATURE contains # a MAPIUID structure that indicates the service provider # responsible for the object. If the PR_MAPPING_SIGNATURE # property is the same for two objects, assume that these # objects use the same name-to-identifier mapping. # [MarkH: MAPIUID objects are supported and hashable] # XXX If the SpamProb (Hammie, whatever) property is passed in as an # XXX int, Outlook displays the field as all blanks, and sorting on # XXX it doesn't do anything, etc. I don't know why. Since I'm # XXX running Python 2.2.2, the _MapiTypeMap above confuses ints # XXX with bools, but the problem persists even if I comment out the # XXX PT_BOOLEAN entry from that dict. Dumping in prints below show # XXX that type_tag is 3 then, and that matches the defn of PT_I4 in # XXX my system header files. # XXX Later: This works after all, but the field shows up as all # XXX blanks unless I *first* modify the view (like Messages) in # XXX Outlook to define a custom Integer field of the same name. self._EnsureObject() try: if type(prop) != type(0): props = ( (mapi.PS_PUBLIC_STRINGS, prop), ) propIds = self.mapi_object.GetIDsFromNames(props, mapi.MAPI_CREATE) type_tag = _MapiTypeMap.get(type(val)) if type_tag is None: raise ValueError, "Don't know what to do with '%r' ('%s')" % ( val, type(val)) prop = PROP_TAG(type_tag, PROP_ID(propIds[0])) help_test_suite("MAPIMsgStoreMsg.SetField") if val is None: # Delete the property self.mapi_object.DeleteProps((prop,)) else: self.mapi_object.SetProps(((prop,val),)) self.dirty = True except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def GetField(self, prop): # xxx - still raise_errors? self._EnsureObject() if type(prop) != type(0): props = ( (mapi.PS_PUBLIC_STRINGS, prop), ) prop = self.mapi_object.GetIDsFromNames(props, 0)[0] if PROP_TYPE(prop) == PT_ERROR: # No such property return None prop = PROP_TAG( PT_UNSPECIFIED, PROP_ID(prop)) try: hr, props = self.mapi_object.GetProps((prop,), 0) ((tag, val), ) = props if PROP_TYPE(tag) == PT_ERROR: if val == mapi.MAPI_E_NOT_ENOUGH_MEMORY: # Too big for simple properties - get via a stream return GetPropFromStream(self.mapi_object, prop) return None return val except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def GetReadState(self): val = self.GetField(PR_MESSAGE_FLAGS) return (val&MSGFLAG_READ) != 0 def SetReadState(self, is_read): try: self._EnsureObject() # always try and clear any pending delivery reports of read/unread help_test_suite("MAPIMsgStoreMsg.SetReadState") if is_read: self.mapi_object.SetReadFlag(USE_DEFERRED_ERRORS|SUPPRESS_RECEIPT) else: self.mapi_object.SetReadFlag(USE_DEFERRED_ERRORS|CLEAR_READ_FLAG) if __debug__: if self.GetReadState() != is_read: print "MAPI SetReadState appears to have failed to change the message state" print "Requested set to %s but the MAPI field after was %r" % \ (is_read, self.GetField(PR_MESSAGE_FLAGS)) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def Save(self): assert self.dirty, "asking me to save a clean message!" # It seems that *not* specifying mapi.MAPI_DEFERRED_ERRORS solves a lot # problems! So we don't! try: help_test_suite("MAPIMsgStoreMsg.Save") self.mapi_object.SaveChanges(mapi.KEEP_OPEN_READWRITE) self.dirty = False except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def _DoCopyMove(self, folder, isMove): assert not self.dirty, \ "asking me to move a dirty message - later saves will fail!" try: dest_folder = self.msgstore._OpenEntry(folder.id) source_folder = self.msgstore._OpenEntry(self.folder_id) flags = 0 if isMove: flags |= MESSAGE_MOVE eid = self.id[1] help_test_suite("MAPIMsgStoreMsg._DoCopyMove") source_folder.CopyMessages((eid,), None, dest_folder, 0, None, flags) # At this stage, I think we have lost meaningful ID etc values # Set everything to None to make it clearer what is wrong should # this become an issue. We would need to re-fetch the eid of # the item, and set the store_id to the dest folder. self.id = None self.folder_id = None except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def MoveTo(self, folder): self._DoCopyMove(folder, True) def CopyTo(self, folder): self._DoCopyMove(folder, False) # Functions to perform operations, but report the error (ONCE!) to the # user. Any errors are re-raised so the caller can degrade gracefully if # necessary. # XXX - not too happy with these - they should go, and the caller should # handle (especially now that we work exclusively with exceptions from # this module. def MoveToReportingError(self, manager, folder): try: self.MoveTo(folder) except MsgStoreException, details: ReportMAPIError(manager, _("Moving a message"), details.mapi_exception) def CopyToReportingError(self, manager, folder): try: self.MoveTo(folder) except MsgStoreException, details: ReportMAPIError(manager, _("Copying a message"), details.mapi_exception) def GetFolder(self): # return a folder object with the parent, or None folder_id = (mapi.HexFromBin(self.folder_id[0]), mapi.HexFromBin(self.folder_id[1])) return self.msgstore.GetFolder(folder_id) def RememberMessageCurrentFolder(self): self._EnsureObject() try: folder = self.GetFolder() # Also save this information in our messageinfo database, which # means that restoring should work even with IMAP. self.original_folder = folder.id[0], folder.id[1] props = ( (mapi.PS_PUBLIC_STRINGS, "SpamBayesOriginalFolderStoreID"), (mapi.PS_PUBLIC_STRINGS, "SpamBayesOriginalFolderID") ) resolve_ids = self.mapi_object.GetIDsFromNames(props, mapi.MAPI_CREATE) prop_ids = PROP_TAG( PT_BINARY, PROP_ID(resolve_ids[0])), \ PROP_TAG( PT_BINARY, PROP_ID(resolve_ids[1])) prop_tuples = (prop_ids[0],folder.id[0]), (prop_ids[1],folder.id[1]) self.mapi_object.SetProps(prop_tuples) self.dirty = True except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def GetRememberedFolder(self): props = ( (mapi.PS_PUBLIC_STRINGS, "SpamBayesOriginalFolderStoreID"), (mapi.PS_PUBLIC_STRINGS, "SpamBayesOriginalFolderID") ) try: self._EnsureObject() resolve_ids = self.mapi_object.GetIDsFromNames(props, mapi.MAPI_CREATE) prop_ids = PROP_TAG( PT_BINARY, PROP_ID(resolve_ids[0])), \ PROP_TAG( PT_BINARY, PROP_ID(resolve_ids[1])) hr, data = self.mapi_object.GetProps(prop_ids,0) if hr != 0: return None (store_tag, store_id), (eid_tag, eid) = data folder_id = mapi.HexFromBin(store_id), mapi.HexFromBin(eid) help_test_suite("MAPIMsgStoreMsg.GetRememberedFolder") return self.msgstore.GetFolder(folder_id) except: # Try to get it from the message info database, if possible if self.original_folder: return self.msgstore.GetFolder(self.original_folder) print "Error locating origin of message", self return Nonedef test(): from win32com.client import Dispatch outlook = Dispatch("Outlook.Application") inbox = outlook.Session.GetDefaultFolder(constants.olFolderInbox) folder_id = inbox.Parent.StoreID, inbox.EntryID store = MAPIMsgStore() for folder in store.GetFolderGenerator([folder_id,], True): print folder for msg in folder.GetMessageGenerator(): print msg store.Close()if __name__=='__main__': test()
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -