#
# 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, <Deleted folder UID as string> )
# 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] <suggested headers>
# 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: <condition> <textin>
# 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 <action> <argument>
# 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()
syntax highlighted by Code2HTML, v. 0.9.1