# # Incoming mail filters. # # # Some things need to be added: # * action mark message (with mark and/or as read) # * ability to temporarily disable a filter # CONDITION_EQUAL = 0 CONDITION_NOTEQUAL = 1 CONDITION_CONTAINS = 2 CONDITION_LARGERTHAN = 3 CONDITION_REGEX_MATCH = 4 CONDITION_MATCH_ALL = 5 ACTION_MOVE = 0 ACTION_LEAVE = 1 ACTION_DELETE = 2 ACTION_FORWARD = 3 ACTION_LEAVE_COLLECT = 4 f_headers = [ "to", "from", "subject", "x-from_", "envelope-to" ] f_conditions = [ "equals", "not equals", "contains", "size > (Kb)", "regex match", "match all" ] f_actions = [ "Move to folder:", "Leave on server", "Delete from server", "Forward to address:", "Collect & leave on server"] import time import re import gtk import ptk.folder_tree from pyneheaders import * import pynemsg import boxtypes import pynei18n from boxtypes import superbox import utils # a filter is a tuple like so: # ( string name, string header to match, integer condition, # string value to match, integer action, string action argument ) # eg: ( "Ignore annoying guy", "from", CONDITION_EQUAL, "twat@aol.com", # ACTION_MOVE, ) # will move mail from 'twat@aol.com' to the deleted folder def filter_collect(user, _mailbox, connection, index): """ Collect message 'index' from `connection`, applying filters. """ def _do_server_policy(msg, _mailbox=_mailbox, index=index, connection=connection): """ What do we want to do with the message on the server now it has been collected? """ if _mailbox.collect_policy[0] == SERVER_DELETE: connection.delete_msg(index) return elif _mailbox.collect_policy[0] == SERVER_LEAVE: return elif _mailbox.collect_policy[0] == SERVER_EXPIRE: # Delete if older than some days... days = _mailbox.collect_policy[1] if days == None: return howold = utils.days_between(time.time(), msg.date) #print howold #if msg.headers.has_key("date"): # print msg.headers["date"] if howold >= days: print "Expiring "+str(index) connection.delete_msg(index) # return message r = "" # download headers only ATM msg = pynemsg.pynemsg() head = connection.get_header(index) if head == None: return "" msg.body = head + "\n\n" # have we appended the body yet? gotbody = 0 # Parse headers msg.parseheaders(user, headers_only=1) msg.date_received = int(time.time()) # Have we already downloaded the thing? # XXX broken if we direct stuff to other boxes if msg.headers["message-id"] in _mailbox.messages: #print "Already got "+msg.headers["message-id"] _do_server_policy(msg) return r # Does it match any filters (multiple matches possible) nomatches = 1 for filtr in _mailbox.filters: name, head, cond, carg, actn, aarg = filtr match = 0 # this condition tests not filthy headers... if cond != CONDITION_LARGERTHAN: if not msg.headers.has_key(head): print "Debug: filter: no such header (%s)" % head continue head = msg.headers[head] #print "Debug: Comparing %s with %s." % (repr(head.lower()), repr(carg.lower())) if cond == CONDITION_LARGERTHAN: # get body and check size if isinstance(_mailbox, boxtypes.nntpbox.newsgroup): # don't bother if we are only collecting headers if (_mailbox.opts & superbox.OPT_HEADERS_ONLY): continue if gotbody == 0: body = connection.get_body(index) msg.opts = msg.opts & (~MSG_NO_BODY) msg.body = msg.body + body gotbody = 1 ## including headers XXX if len(msg.body) > (1024*int(carg)): match = 1 elif isinstance(_mailbox, boxtypes.mailbox.mailbox): # get message size from our {id: size} message dict size = _mailbox.pending_msgs[index] if int(size) > (1024*int(carg)): match = 1 elif cond == CONDITION_EQUAL: if head.lower() == carg.lower(): match = 1 elif cond == CONDITION_NOTEQUAL: if head.lower() != carg.lower(): match = 1 elif cond == CONDITION_CONTAINS: if head.lower().find(carg.lower()) != -1: match = 1 elif cond == CONDITION_REGEX_MATCH: if re.match(carg.lower(), head.lower()) != None: match = 1 elif cond == CONDITION_MATCH_ALL: match = 1 #if match: print "Match found" #else: print "NO MATCH" if match == 1: r = "\""+name+"\" "+_("filter match.") nomatches = 0 # It matched a filter. do action if actn == ACTION_MOVE: # get body if (gotbody == 0) and not (_mailbox.opts & superbox.OPT_HEADERS_ONLY): body = connection.get_body(index) msg.body = msg.body + body msg.opts = msg.opts & (~MSG_NO_BODY) gotbody = 1 # get folder to move to, from UID folder = user.get_folder_by_uid(aarg) if folder == None: print "FILTER: Error. move_to folder not found." print "FILTER: "+str(filtr) _mailbox.num_unread += 1 _mailbox.save_new_article(msg) _do_server_policy(msg) else: # Add message to mailbox folder.num_unread += 1 folder.save_new_article(msg) # What to do with copy on server _do_server_policy(msg) # Mark folder for update folder.changed = 1 # some obsoleted comment clinging to existence return r elif actn == ACTION_LEAVE: # leave on server. don't store return r elif actn == ACTION_DELETE: # Delete from server. don't store connection.delete_msg(index) return r elif actn == ACTION_FORWARD: # get body if (gotbody == 0) and not (_mailbox.opts & superbox.OPT_HEADERS_ONLY): body = connection.get_body(index) msg.body = msg.body + body msg.opts = msg.opts & (~MSG_NO_BODY) gotbody = 1 msg.parseheaders(user) outbox = user.get_folder_by_uid("outbox") outbox._reply(user, _mailbox, _mailbox, msg, \ REPLY_FORWARD, send_to=aarg, dont_open_editbox=1) # What to do with copy on server _do_server_policy(msg) return r elif actn == ACTION_LEAVE_COLLECT: # collect but leave on server if (gotbody == 0) and not (_mailbox.opts & superbox.OPT_HEADERS_ONLY): body = connection.get_body(index) msg.body = msg.body + body msg.opts = msg.opts & (~MSG_NO_BODY) gotbody = 1 # Add message to mailbox _mailbox.num_unread += 1 _mailbox.save_new_article(msg) return r if nomatches == 1: # default action. store to mailbox and trash # from server if (gotbody == 0) and not (_mailbox.opts & superbox.OPT_HEADERS_ONLY): body = connection.get_body(index) msg.body = msg.body + body msg.opts = msg.opts & (~MSG_NO_BODY) gotbody = 1 # Add message to mailbox _mailbox.num_unread += 1 _mailbox.save_new_article(msg) # What to do with copy on server _do_server_policy(msg) return r class filter_editbox: """ A gtk.Box full of shit for editing a mailbox.filters """ def sel_folder(self, w, event, tree): folder = self.user.get_folder_by_uid(tree.user_selected) if folder != None: self.aarg_in.set_text(folder.name) self.f_aarg = folder.uid self.update_state() def ch_condition(self, _a): self.f_cond = self.cond_combo.get_active () def ch_header(self, _a): self.f_head = self.header_combo.get_active () self.update_state() def update_state(self): # f_actn has changed. update widgets. # also f_head if self.f_head == len(f_headers): self.header_in.show() else: self.header_in.hide() if self.f_actn == ACTION_MOVE: self.folder_list.show() self.aarg_in.show() self.aarg_in.set_sensitive(False) # No folder selected to move to. BAD!! folder = self.user.get_folder_by_uid(self.f_aarg) if folder == None: self.aarg_in.set_text("") self.apply_button.set_sensitive(False) self.ok_button.set_sensitive(False) else: self.aarg_in.set_text(folder.name) self.apply_button.set_sensitive(True) self.ok_button.set_sensitive(True) elif self.f_actn == ACTION_FORWARD: self.folder_list.hide() self.aarg_in.set_text(self.f_aarg) self.aarg_in.show() self.aarg_in.set_sensitive(True) elif self.f_actn == ACTION_LEAVE or self.f_actn == ACTION_DELETE \ or self.f_actn == ACTION_LEAVE_COLLECT: self.folder_list.hide() self.aarg_in.hide() def ch_action(self, item): self.f_actn = self.act_combo.get_active () self.update_state() def __init__(self, user, filter_list_box, index): import copy self.user = user self.filter = copy.deepcopy(filter_list_box.filters[index]) self.filter_index = index self.user = filter_list_box.user self.filter_list_box = filter_list_box # Load filter into widgets self.f_name = self.filter[0] if self.filter[1] in f_headers: self.f_head = f_headers.index(self.filter[1]) else: # Custom... self.f_head = len(f_headers) self.f_cond = self.filter[2] self.f_carg = self.filter[3] self.f_actn = self.filter[4] self.f_aarg = self.filter[5] self.win = gtk.Window() self.win.set_title(_("Editing filter")) self.win.set_size_request(400,400) box = gtk.VBox(spacing=5) box.set_border_width(5) self.win.add(box) box.show() # Disgusting over-use of gtk.Boxes seems like a nice way # of constructing this ;-) box0 = gtk.HBox(spacing=5) ; box0.set_border_width(5) box1 = gtk.HBox(spacing=5) ; box1.set_border_width(5) box2 = gtk.HBox(spacing=5) ; box2.set_border_width(5) box3 = gtk.HBox(spacing=5) ; box3.set_border_width(5) box4 = gtk.HBox() box4_1 = gtk.VBox(spacing=5) ; box4_1.set_border_width(5) box4_2 = gtk.VBox(spacing=5) ; box4_2.set_border_width(5) # Box0: filter name label = gtk.Label(_("Filter name: ")) box0.pack_start(label, expand=False) label.show() self.name_in = gtk.Entry() box0.pack_start(self.name_in) self.name_in.show() box.pack_start(box0, expand=False) box0.show() # Box1: if [text-in] # eg: if "subject" label = gtk.Label(_("If header")) box1.pack_start(label, expand=False) label.show() self.header_combo = gtk.combo_box_new_text () for i in range (0, len (f_headers)): self.header_combo.append_text (f_headers[i]) if self.f_head == i: self.header_combo.set_active (i) self.header_combo.append_text ("Custom:") self.header_combo.connect ("changed", self.ch_header) box1.pack_start(self.header_combo, expand=False) self.header_combo.show () self.header_in = gtk.Entry() box1.pack_start(self.header_in) self.header_in.show() box.pack_start(box1, expand=False) box1.show() # Box2: # eg: Equals "Here to torment you" self.cond_combo = gtk.combo_box_new_text () for i in range (0, len (f_conditions)): self.cond_combo.append_text (f_conditions[i]) self.cond_combo.set_active (self.f_cond) self.cond_combo.connect ("changed", self.ch_condition) box2.pack_start(self.cond_combo, expand=False) self.cond_combo.show () self.carg_in = gtk.Entry() box2.pack_end(self.carg_in) self.carg_in.show() box.pack_start(box2, expand=False) box2.show() # Box3: then # eg: then "move to" deleted label = gtk.Label(_("then")) box3.pack_start(label, expand=False) label.show() self.act_combo = gtk.combo_box_new_text () for i in range (0, len (f_actions)): self.act_combo.append_text (f_actions[i]) self.act_combo.set_active (self.f_actn) self.act_combo.connect ("changed", self.ch_action) box3.pack_start (self.act_combo, expand=False) self.act_combo.show () self.aarg_in = gtk.Entry() box3.pack_end(self.aarg_in) self.aarg_in.show() box.pack_start(box3, expand=False) box3.show() # Box4_1: bunch of buttons and folder selector # for move_to rule self.folder_list = ptk.folder_tree.folder_tree(user) self.folder_list.update(self.user, update_all=1) self.folder_list.connect_button_press_event(self.sel_folder) swin = gtk.ScrolledWindow() swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) swin.show() swin.add(self.folder_list) box4_1.pack_start(swin)#, expand=False) swin.show() self.folder_list_win = swin def _save_filter(_button, self=self): self.f_name = self.name_in.get_text() if self.f_head < len(f_headers): # predefined header head_to_match = f_headers[self.f_head] else: # custom one head_to_match = self.header_in.get_text() # (if action is move then f_aarg is already a folder UID) if self.f_actn != ACTION_MOVE: self.f_aarg = self.aarg_in.get_text() self.f_carg = self.carg_in.get_text() self.filter = (self.f_name, head_to_match, self.f_cond, self.f_carg, self.f_actn, self.f_aarg) self.filter_list_box.filters[self.filter_index] = self.filter self.filter_list_box.update_filter_list() def _save_filter_close(_button, self=self, _save_filter=_save_filter): _save_filter(_button) # remove this window from the list self.filter_list_box.edit_windows.remove(self) self.win.destroy() def _cancel(_button, self=self): self.win.destroy() # buttons horizontal in yet another box... button_box = gtk.HBox(spacing=5) box.pack_end(button_box, expand=False) button_box.show() button = gtk.Button(stock="gtk-cancel") button.connect("clicked", _cancel) button_box.pack_end(button, expand=False) button.show() button = gtk.Button(stock="gtk-apply") button.connect("clicked", _save_filter) button_box.pack_end(button, expand=False) button.show() self.apply_button = button button = gtk.Button(stock="gtk-ok") button.connect("clicked", _save_filter_close) button_box.pack_end(button, expand=False) button.show() self.ok_button = button box.pack_start(box4) box4.show() box4.pack_start(box4_1, expand=False) box4_1.show() # Stuff the Gtk thingies with filter junk self.name_in.set_text(self.f_name) self.header_in.set_text(self.filter[1]) self.carg_in.set_text(self.f_carg) self.update_state() self.win.show() class filters_editlist(gtk.VBox): """ A gtk.Box full of shit for editing a mailbox.filters """ def update_filter_list(self): self.filter_list.freeze() self.filter_list.clear() for x in self.filters: self.filter_list.append( [ x[0] ] ) self.filter_list.thaw() def move_filter(self, old_pos, new_pos): # Correct filter index of edit windows for win in self.edit_windows: if win.filter_index == old_pos: win.filter_index = new_pos else: if win.filter_index >= new_pos: win.filter_index = win.filter_index + 1 elif win.filter_index > old_pos and win.filter_index <= new_pos: win.filter_index = win.filter_index - 1 filter = self.filters[old_pos] del self.filters[old_pos] self.filters.insert(new_pos, filter) self.update_filter_list() def apply_changes(self): self.box.filters = self.filters def __init__(self, mailbox, user): gtk.VBox.__init__(self, spacing=5) self.box = mailbox self.set_border_width(5) self.user = user import copy self.filters = copy.deepcopy(mailbox.filters) self.cur_edit = None # currently editing which filter? # Windows open editing filters self.edit_windows = [] # Last item selected by the user self.user_selected = [] # Widgets to keep disabled until a filter is selected self.disabled_until_selection = [] # buttons horizontal in yet another box... button_box = gtk.HBox(spacing=5) self.pack_end(button_box, expand=False) button_box.show() def new_filter(button, self=self): self.filters.append(("New filter","to",CONDITION_EQUAL,"",ACTION_MOVE,"deleted")) self.update_filter_list() self.edit_windows.append(filter_editbox(self.user, self, len(self.filters)-1)) button = gtk.Button(stock="gtk-new") button.connect("clicked", new_filter) button_box.pack_end(button, expand=False) button.show() def copy_filter(button, self=self): x = self.filter_list.selection[0] self.filters.append(self.filters[x]) self.update_filter_list() button = gtk.Button(stock="gtk-copy") button.connect("clicked", copy_filter) button_box.pack_end(button, expand=False) # disabled until you click on an item button.set_sensitive(False) button.show() self.disabled_until_selection.append(button) def edit_filter(button, self=self): i = self.filter_list.selection[0] self.edit_windows.append(filter_editbox(self.user, self, i)) button = gtk.Button(" "+_("Edit")+" ") button.connect("clicked", edit_filter) button_box.pack_end(button, expand=False) # disabled until you click on an item button.set_sensitive(False) button.show() self.disabled_until_selection.append(button) def delete_filter(button, self=self): index = self.filter_list.selection[0] # delete edit windows that are open and editing this filter for win in self.edit_windows: if win.filter_index == index: win.win.destroy() del win elif win.filter_index > index: win.filter_index = win.filter_index - 1 del self.filters[index] self.update_filter_list() button = gtk.Button(stock="gtk-delete") button.connect("clicked", delete_filter) button_box.pack_end(button, expand=False) # disabled until you click on an item button.set_sensitive(False) button.show() self.disabled_until_selection.append(button) def _clicked_filter(_button, event, self=self): # What did we select? selected = self.filter_list.get_selection_info(int(event.x), int(event.y)) if selected: # Enable some buttons for widget in self.disabled_until_selection: widget.set_sensitive(True) else: for widget in self.disabled_until_selection: widget.set_sensitive(False) return index = selected[0] self.user_selected = [index] # Open edit window on double-click if event.type == gtk.gdk._2BUTTON_PRESS: # Open filter edit box self.edit_windows.append(filter_editbox(self.user, self, index)) def _dnd_drag_data_get(w, context, selection_data, info, time, self=self): # selected = str(self.user_selected[0]) selection_data.set(selection_data.target, 8, selected) def _dnd_drag_motion(w, context, x, y, time, self=self): # get target (pyne_filter) content_type = str(context.targets[0]) y = y-20 # see mainwin for similar hack... selected = self.filter_list.get_selection_info(x,y) if selected: row = selected[0] self.filter_list.select_row(row, 0) def _dnd_drag_data_received(w, context, x, y, data, info, time, self=self): # get target content_type = str(context.targets[0]) old_pos = int(data.data) if not self.filter_list.selection: return new_pos = self.filter_list.selection[0] # Move filter from old_pos to new_pos self.move_filter(old_pos, new_pos) targets = [ ("pyne_filter", 0, -1) ] # Box4_2: List of rules self.filter_list = gtk.CList(1, [ _("Filters (highest evaluated first)") ]) self.filter_list.connect("button_press_event", _clicked_filter) self.filter_list.connect("drag_data_received", _dnd_drag_data_received) self.filter_list.drag_dest_set(gtk.DEST_DEFAULT_ALL, targets, gtk.gdk.ACTION_MOVE) self.filter_list.connect("drag_motion", _dnd_drag_motion) self.filter_list.connect("drag_data_get", _dnd_drag_data_get) self.filter_list.drag_source_set(gtk.gdk.BUTTON1_MASK|gtk.gdk.BUTTON3_MASK, targets, gtk.gdk.ACTION_MOVE) swin = gtk.ScrolledWindow() swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) swin.show() swin.add(self.filter_list) self.filter_list.show() self.pack_start(swin) swin.show() # prepare for input self.update_filter_list() class policy_editbox(gtk.VBox): def __init__(self, box): self.box = box self.collect_policy = list(box.collect_policy) self.collect_policy_entry = gtk.Entry() self.collect_policy_entry.set_text(str(self.collect_policy[1])) # Server policy thing gtk.VBox.__init__(self, spacing=5) self.set_border_width(5) self.policy_combo = gtk.combo_box_new_text () for i in server_policies: self.policy_combo.append_text (i) self.policy_combo.set_active ( self.collect_policy [0] ) self.policy_combo.connect ("changed", self.ch_server_policy) self.policy_combo.show () hbox = gtk.HBox(spacing=5) self.pack_start(hbox, expand=False) hbox.pack_end(self.collect_policy_entry) self.collect_policy_entry.show() self.update_entry() label = gtk.Label(_("After collection:")) hbox.pack_start(label, expand=False) hbox.pack_start(self.policy_combo, expand=False) label.show() hbox.show() self.policy_combo.show() def apply_changes(self): self.collect_policy[1] = self.collect_policy_entry.get_text() # Should be integer days if SERVER_EXPIRE if self.collect_policy[0] == SERVER_EXPIRE: try: self.collect_policy[1] = int(self.collect_policy[1]) except ValueError: # default to bix expire_after on failure to parse self.collect_policy[1] = self.box.expire_after self.box.collect_policy = tuple(self.collect_policy) def update_entry(self): if self.collect_policy[0] == SERVER_EXPIRE: self.collect_policy_entry.show() else: self.collect_policy_entry.hide() def ch_server_policy(self, item): self.collect_policy[0] = self.policy_combo.get_active () self.update_entry()