📄 addin.py
字号:
# SpamBayes Outlook Addinimport sys, osimport typesimport warningsimport tracebackimport _winregfrom types import UnicodeType# *sigh* - this is for the binary installer, and for the sake of one line# that is implicit anyway, I gave upimport encodingstry: True, Falseexcept NameError: # Maintain compatibility with Python 2.2 True, False = 1, 0# We have lots of locale woes. The short story:# * Outlook/MAPI will change the locale on us as some predictable# times - but also at unpredictable times.# * Python currently insists on "C" locale - if it isn't, subtle things break,# such as floating point constants loaded from .pyc files.# * Our config files also want a consistent locale, so periods and commas# are the same when they are read as when they are written.# So, at a few opportune times, we simply set it back.# We do it here as early as possible, before any imports that may see this## See also [725466] Include a proper locale fix in Options.py,# assorted errors relating to strange math errors, and spambayes-dev archives,# starting July 23 2003.import localelocale.setlocale(locale.LC_NUMERIC, "C")from win32com import universalfrom win32com.server.exception import COMExceptionfrom win32com.client import gencache, DispatchWithEvents, Dispatchimport win32apiimport pythoncomfrom win32com.client import constants, geteventsimport win32gui, win32con, win32clipboard # for button images!import timer, threadfrom dialogs.dlgutils import SetWaitCursortoolbar_name = "SpamBayes"ADDIN_DISPLAY_NAME = "SpamBayes Outlook Addin"# If we are not running in a console, redirect all print statements to the# win32traceutil collector.# You can view output either from Pythonwin's "Tools->Trace Collector Debugging Tool",# or simply run "win32traceutil.py" from a command prompt.try: win32api.GetConsoleTitle()except win32api.error: # No console - if we are running from Python sources, # redirect to win32traceutil, but if running from a binary # install, redirect to a log file. # Want to move to logging module later, so for now, we # hack together a simple logging strategy. if hasattr(sys, "frozen"): temp_dir = win32api.GetTempPath() for i in range(3,0,-1): try: os.unlink(os.path.join(temp_dir, "spambayes%d.log" % (i+1))) except os.error: pass try: os.rename( os.path.join(temp_dir, "spambayes%d.log" % i), os.path.join(temp_dir, "spambayes%d.log" % (i+1)) ) except os.error: pass # Open this log, as unbuffered so crashes still get written. sys.stdout = open(os.path.join(temp_dir,"spambayes1.log"), "wt", 0) sys.stderr = sys.stdout else: import win32traceutil# We used to catch COM errors - but as most users are now on the binary, this# niceness doesn't help anyone.# As MarkH assumed, and later found to back him up in:# http://www.slipstick.com/dev/comaddins.htm:# On building add-ins for multiple Outlook versions, Randy Byrne writes in# the microsoft.public.office.developer.com.add_ins newsgroup, "The best# practice is to compile your Add-in with OL2000 and MSO9.dll. Then your# add-in will work with both OL2000 and OL2002, and CommandBar events will# work with both versions. If you need to use any specific OL2002 or# Office 10.0 Object Library calls, you can use late binding to address# those issues. The CommandBar Events are the same in both Office# 2000 and Office XP."# So that is what we do: specify the minimum versions of the typelibs we# can work with - ie, Outlook 2000.# win32com generally checks the gencache is up to date (typelib hasn't# changed, makepy hasn't changed, etc), but when frozen we dont want to# do this - not just for perf, but because they don't always exist!bValidateGencache = not hasattr(sys, "frozen")# Generate support so we get complete support including eventsgencache.EnsureModule('{00062FFF-0000-0000-C000-000000000046}', 0, 9, 0, bForDemand=True, bValidateFile=bValidateGencache) # Outlook 9gencache.EnsureModule('{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}', 0, 2, 1, bForDemand=True, bValidateFile=bValidateGencache) # Office 9# We the "Addin Designer" typelib for its constantsgencache.EnsureModule('{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}', 0, 1, 0, bForDemand=True, bValidateFile=bValidateGencache)# ... and also for its _IDTExtensibility2 vtable interface.universal.RegisterInterfaces('{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}', 0, 1, 0, ["_IDTExtensibility2"])try: from win32com.client import CastTo, WithEventsexcept ImportError: print "*" * 50 print "You appear to be running a win32all version pre 151, which is pretty old" print "I'm afraid it is time to upgrade" raise# we seem to have all the COM support we need - let's rock!# Determine if we have ever seen a message before. If we have saved the spam# field, then we know we have - but saving the spam field is an option (and may# fail, depending on the message store). So if no spam field, we check if# ever been trained on.def HaveSeenMessage(msgstore_message, manager): if msgstore_message.GetField(manager.config.general.field_score_name) is not None: return True # If the message has been trained on, we certainly have seen it before. import train manager.classifier_data.message_db.load_msg(msgstore_message) if train.been_trained_as_ham(msgstore_message) or \ train.been_trained_as_spam(msgstore_message): return True # I considered checking if the "save spam score" option is enabled - but # even when enabled, this sometimes fails (IMAP, hotmail) # Best we can do now is to assume if it is read, we have seen it. return msgstore_message.GetReadState()# Helper functionsdef TrainAsHam(msgstore_message, manager, rescore = True, save_db = True): import train subject = msgstore_message.subject print "Training on message '%s' in '%s - " % \ (subject, msgstore_message.GetFolder().GetFQName()), if train.train_message(msgstore_message, False, manager.classifier_data): print "trained as good" # Simplest way to rescore is to re-filter with all_actions = False if rescore: import filter filter.filter_message(msgstore_message, manager, all_actions = False) else: print "already was trained as good" manager.classifier_data.message_db.load_msg(msgstore_message) assert train.been_trained_as_ham(msgstore_message) if save_db: manager.classifier_data.SavePostIncrementalTrain()def TrainAsSpam(msgstore_message, manager, rescore = True, save_db = True): import train subject = msgstore_message.subject print "Training on message '%s' in '%s - " % \ (subject, msgstore_message.GetFolder().GetFQName()), if train.train_message(msgstore_message, True, manager.classifier_data): print "trained as spam" # Simplest way to rescore is to re-filter with all_actions = False if rescore: import filter filter.filter_message(msgstore_message, manager, all_actions = False) else: print "already was trained as spam" manager.classifier_data.message_db.load_msg(msgstore_message) assert train.been_trained_as_spam(msgstore_message) # And if the DB can save itself incrementally, do it now if save_db: manager.classifier_data.SavePostIncrementalTrain()# Function to filter a message - note it is a msgstore msg, not an# outlook onedef ProcessMessage(msgstore_message, manager): manager.LogDebug(2, "ProcessMessage starting for message '%s'" \ % msgstore_message.subject) try: if not msgstore_message.IsFilterCandidate(): manager.LogDebug(1, "Skipping message '%s' - we don't filter ones like that!" \ % msgstore_message.subject) return if HaveSeenMessage(msgstore_message, manager): # Already seen this message - user probably moving it back # after incorrect classification. # If enabled, re-train as Ham # otherwise just ignore. if manager.config.training.train_recovered_spam: import train manager.classifier_data.message_db.load_msg(msgstore_message) if train.been_trained_as_spam(msgstore_message): need_train = True else: prop = msgstore_message.GetField(manager.config.general.field_score_name) # We may not have been able to save the score - re-score now if prop is None: prop = manager.score(msgstore_message) # If it was not previously classified as either 'Spam' or # 'Unsure', then this event is unlikely to be the user # re-classifying (and in fact it may simply be the Outlook # rules moving the item). need_train = manager.config.filter.unsure_threshold < prop * 100 if need_train: TrainAsHam(msgstore_message, manager) else: subject = msgstore_message.subject manager.LogDebug(1, "Message '%s' was previously seen, but " \ "did not need to be trained as ham" % subject) return if manager.config.filter.enabled: import filter # get the foldername before the move operation! folder_name = msgstore_message.GetFolder().GetFQName() disposition = filter.filter_message(msgstore_message, manager) print "Message '%s' in '%s' had a Spam classification of '%s'" \ % (msgstore_message.GetSubject(), folder_name, disposition) manager.HandleNotification(disposition) else: print "Spam filtering is disabled - ignoring new message" except manager.message_store.NotFoundException: manager.LogDebug(1, "ProcessMessage had the message moved out from underneath us") manager.LogDebug(2, "ProcessMessage finished for", msgstore_message.subject)# Button/Menu and other UI event handler classesclass ButtonEvent: def Init(self, handler, *args): self.handler = handler self.args = args def Close(self): self.handler = self.args = None def OnClick(self, button, cancel): # Callback from Outlook - locale may have changed. locale.setlocale(locale.LC_NUMERIC, "C") # see locale comments above self.handler(*self.args)# Folder event handler classesclass _BaseItemsEvent: def Init(self, target, application, manager): self.owner_thread_ident = thread.get_ident() # check we arent multi-threaded self.application = application self.manager = manager self.target = target self.use_timer = False def ReInit(self): pass def Close(self): self.application = self.manager = self.target = None self.close() # the eventsclass HamFolderItemsEvent(_BaseItemsEvent): def Init(self, *args): _BaseItemsEvent.Init(self, *args) timer_enabled = self.manager.config.filter.timer_enabled start_delay = self.manager.config.filter.timer_start_delay interval = self.manager.config.filter.timer_interval use_timer = timer_enabled and start_delay and interval if timer_enabled and not use_timer: print "*" * 50 print "The timer is enabled, but one of the timer intervals values is zero" print "You must set both intervals before the timer will enable" if use_timer and not hasattr(timer, "__version__"): # No binaries will see this. print "*" * 50 print "SORRY: You have tried to enable the timer, but you have a" print "leaky version of the 'timer' module. These leaks prevent" print "Outlook from shutting down. Please update win32all to post 154"
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -