Re: [PATCH 9/9] add has: query prefix to search for specific properties
[notmuch-archives.git] / fa / 01e49725dec4d8cc71c7d4fbc7879c5e64ec8a
1 Return-Path: <bgamari.foss@gmail.com>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id BC2EF431FB6\r
6         for <notmuch@notmuchmail.org>; Wed, 23 Feb 2011 06:23:06 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -0.799\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.799 tagged_above=-999 required=5\r
12         tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
13         FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id ODwLvKJv1aog for <notmuch@notmuchmail.org>;\r
17         Wed, 23 Feb 2011 06:23:03 -0800 (PST)\r
18 Received: from mail-qw0-f53.google.com (mail-qw0-f53.google.com\r
19         [209.85.216.53]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
20         (No client certificate requested)\r
21         by olra.theworths.org (Postfix) with ESMTPS id D4753431FB5\r
22         for <notmuch@notmuchmail.org>; Wed, 23 Feb 2011 06:23:02 -0800 (PST)\r
23 Received: by qwc9 with SMTP id 9so3634960qwc.26\r
24         for <notmuch@notmuchmail.org>; Wed, 23 Feb 2011 06:23:00 -0800 (PST)\r
25 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma;\r
26         h=domainkey-signature:from:to:subject:user-agent:date:message-id\r
27         :mime-version:content-type;\r
28         bh=XNXQJ/4qiywr7H3pAoW8UzYwekyO85KZWLvkHnrlqJk=;\r
29         b=Se1aB/X9IBSehMQnTJOTIN4lSTF68cLaw0HSaMoc4DaTZhdMDafvAP1VvAzCoZdOmv\r
30         24+yaju5oq3u548esdb6MQS2KyIP4LHZOeI2JfKDXTg1JOgldS6Th8qQ4HvWnXP2Xngm\r
31         U2XKa6Wyi1Vgi7WniNrw95q61zH04lZIU9olc=\r
32 DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma;\r
33         h=from:to:subject:user-agent:date:message-id:mime-version\r
34         :content-type;\r
35         b=QPYHPHchycWoFKl79YakJTET3RucUNX6+2repdRdOI8UtFY70NIRJ21783CGUVjz0m\r
36         KHKxDRHRYAqKCli98h0hpBWnM269QjUqmFuyVJA2+LnxVhhCAWvVxFBEs9srQ+qgYIKg\r
37         AOCNr28kk8Zr7rFikajK+XYWw4vA88MHtnUOs=\r
38 Received: by 10.229.229.139 with SMTP id ji11mr3041165qcb.174.1298470980042;\r
39         Wed, 23 Feb 2011 06:23:00 -0800 (PST)\r
40 Received: from localhost (gamari.physics.umass.edu [128.119.56.223])\r
41         by mx.google.com with ESMTPS id g28sm5479260qck.37.2011.02.23.06.22.59\r
42         (version=TLSv1/SSLv3 cipher=OTHER);\r
43         Wed, 23 Feb 2011 06:22:59 -0800 (PST)\r
44 From: Ben Gamari <bgamari.foss@gmail.com>\r
45 To: notmuch <notmuch@notmuchmail.org>\r
46 Subject: My mail configuration\r
47 User-Agent: Notmuch/0.5-64-gdd23272 (http://notmuchmail.org) Emacs/23.1.1\r
48         (x86_64-pc-linux-gnu)\r
49 Date: Wed, 23 Feb 2011 09:22:57 -0500\r
50 Message-ID: <87tyfu3k5a.fsf@gmail.com>\r
51 MIME-Version: 1.0\r
52 Content-Type: text/plain; charset=us-ascii\r
53 X-BeenThere: notmuch@notmuchmail.org\r
54 X-Mailman-Version: 2.1.13\r
55 Precedence: list\r
56 List-Id: "Use and development of the notmuch mail system."\r
57         <notmuch.notmuchmail.org>\r
58 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
59         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
60 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
61 List-Post: <mailto:notmuch@notmuchmail.org>\r
62 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
63 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
64         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
65 X-List-Received-Date: Wed, 23 Feb 2011 14:23:06 -0000\r
66 \r
67 Here is my mail sorting script that has been slowly evolving for almost\r
68 a year now. It uses the Python bindings, along with Bogofilter for spam\r
69 filtering. There is also an update-spam script which brings the\r
70 Bogofilter database in to synchronization with the notmuch tags. On this\r
71 note, if someone wants to implement the ability to hide certain tags\r
72 (say, those matching /\..+/) in the emacs interface it would be greatly\r
73 appreciated. I have notmuch configured such that all new mail starts\r
74 with just the "new" tag. The sorting script then takes it from\r
75 there. Hope this will give folks some ideas.\r
76 \r
77 Cheers,\r
78 \r
79 - Ben\r
80 \r
81 \r
82 ===File ~/.env/mail/sort_mail.py============================\r
83 #!/usr/bin/python\r
84 \r
85 # Warning:\r
86 # Be careful about using Query.count_messages(), it's technically an estimate\r
87 # and is not guarranteed to be correct\r
88 \r
89 import os\r
90 import logging\r
91 import time\r
92 \r
93 logging.basicConfig(level=logging.DEBUG)\r
94 \r
95 _tags = []\r
96 start_time = time.time()\r
97 \r
98 def sf_list(name, tag):\r
99         #_tags.append( ('to:%s@lists.sourceforge.net' % name, ['list', tag]) )\r
100         #_tags.append( ('to:%s@lists.sf.net' % name, ['list', tag]) )\r
101         _tags.append( ('to:%s' % name, ['list', tag]) )\r
102 \r
103 def kernel_list(name, tag):\r
104         #_tags.append( ('to:%s@vger.kernel.org' % name, ['list', tag]) )\r
105         _tags.append( ('to:%s' % name, ['list', tag]) )\r
106 \r
107 def fdo_list(name, tag):\r
108         #_tags.append( ('to:%s@lists.freedesktop.org' % name, ['list', tag]) )\r
109         _tags.append( ('to:%s' % name, ['list', tag]) )\r
110 \r
111 def _list(name, tag):\r
112         _tags.append( ('to:%s' % name, ['list', tag]) )\r
113 \r
114 def tag(filter, *tags):\r
115         _tags.append( (filter, tags) )\r
116 \r
117 kernel_list('linux-kernel', 'lkml')\r
118 kernel_list('mm-commits', 'mm-commits')\r
119 kernel_list('linux-omap', 'linux-omap')\r
120 kernel_list('linux-next', 'linux-next')\r
121 kernel_list('linux-wireless', 'linux-wireless')\r
122 kernel_list('linux-btrfs', 'btrfs')\r
123 _list('linux-pm', 'linux-pm')\r
124 _list('linux-arm-kernel', 'linux-arm')\r
125 sf_list('oprofile-list', 'oprofile')\r
126 sf_list('spi-devel-general', 'spi-devel')\r
127 sf_list('linux1394-devel', 'ieee1394')\r
128 \r
129 sf_list('ipw3945-devel', 'ipw')\r
130 _list('hostap@lists.shmoo.com', 'hostap')\r
131 _list('ath9k-devel@', 'ath9k')\r
132 _list('vim-dev@vim.org', 'vim')\r
133 _list('vim_dev', 'vim')\r
134 \r
135 fdo_list('intel-gfx', 'intel-gfx')\r
136 fdo_list('xorg', 'xorg')\r
137 fdo_list('hal', 'hal')\r
138 fdo_list('compiz', 'compiz')\r
139 sf_list('dri-devel', 'dri')\r
140 sf_list('dri-users', 'dri')\r
141 sf_list('mesa3d-dev', 'mesa')\r
142 fdo_list('mesa-dev', 'mesa')\r
143 \r
144 fdo_list('devkit-devel', 'devkit')\r
145 sf_list('matplotlib-users', 'matplotlib')\r
146 sf_list('matplotlib-devel', 'matplotlib')\r
147 _list('notmuch@notmuchmail.org', 'notmuch')\r
148 _list('eigen@lists.tuxfamily.org', 'eigen')\r
149 _list('launchpad-users@lists.launchpad.net', 'launchpad')\r
150 _list('boost@lists.boost.org', 'boost')\r
151 _list('debian-python@lists.debian.org', 'debian-python')\r
152 \r
153 _list('geda-user@', 'geda')\r
154 \r
155 _list('openembedded-devel@lists.openembedded.org', 'openembedded')\r
156 _list('beagleboard@googlegroups.com', 'beagleboard')\r
157 _list('angstrom-distro-devel@linuxtogo.org', 'angstrom')\r
158 _list('angstrom-distro-users@linuxtogo.org', 'angstrom')\r
159 \r
160 _list('mono-devel-list@lists.ximian.com', 'mono')\r
161 _list('mono-list@', 'mono')\r
162 _list('ubuntu-devel-discuss@lists.ubuntu.com', 'ubuntu-devel')\r
163 _list('git@vger.kernel.org', 'git')\r
164 _list('sup-talk@rubyforge.org', 'sup')\r
165 _list('thrust-users@googlegroups.com', 'thrust')\r
166 _list('golang-nuts@googlegroups.com', 'go')\r
167 _list('numpy-discussion@scipy.org', 'numpy')\r
168 _list('scipy-user@scipy.org', 'scipy')\r
169 \r
170 _list('rsync@lists.samba.org', 'rsync')\r
171 tag('from:samba-bugs', 'bugs', 'rsync', 'list')\r
172 \r
173 _list('containers@', 'containers')\r
174 \r
175 tag('from:bugzilla', 'bugs', 'list')\r
176 \r
177 # Tags that aren't for lists\r
178 tag('from:Facebook', 'facebook')\r
179 tag('to:gdh@gdhour.com', 'gdh')\r
180 \r
181 tag('to:bgamari@gmail.com', 'gmail')\r
182 tag('to:bgamari.foss@gmail.com', 'foss')\r
183 tag('from:Ben Gamari', 'sent')\r
184 tag('from:bgamari.foss', 'sent')\r
185 \r
186 from sort_junk import sort_junk\r
187 from notmuch_utils import *\r
188 import notmuch\r
189 db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)\r
190 \r
191 # Freeze new messages\r
192 q_new = notmuch.Query(db, 'tag:new')\r
193 n_msgs = 0\r
194 for msg in q_new.search_messages():\r
195         msg.freeze()\r
196         n_msgs += 1\r
197 \r
198 # Take care of basics\r
199 tag_search(db, 'tag:new', '+unread', '+unseen')\r
200 \r
201 # Take care of feeds\r
202 tag_search(db, 'folder:feeds', '+feeds', '-new')\r
203 \r
204 # Run through Bogofilter\r
205 sort_junk(q_new)\r
206 \r
207 # Tag things\r
208 for filter, tags in _tags:\r
209         tag_search(db, '%s and tag:new' % filter, *tags)\r
210 \r
211 # Ignore things I sent\r
212 tag_search(db, 'tag:new and tag:sent', '-unseen', '-new', '-unread', '+watch')\r
213 \r
214 # Update watch tag\r
215 for msg in q_new.search_messages():\r
216         q = notmuch.Query(db, 'tag:watch and thread:%s' % msg.get_thread_id())\r
217         if len(q.search_messages()) > 0:\r
218                 logging.debug('watching %s' % msg.get_message_id())\r
219                 msg.add_tag('watch')\r
220 \r
221 # Watched items should go to inbox\r
222 tag_search(db, 'tag:new and tag:watch', '+inbox', '-new')\r
223 \r
224 # Ignore threads that I've already seen\r
225 q = notmuch.Query(db, 'tag:new and tag:list')\r
226 for msg in q.search_messages():\r
227         q2 = notmuch.Query(db, 'thread:%s and not tag:unseen' % msg.get_thread_id())\r
228         if len(q2.search_messages()) > 0:\r
229                 msg.remove_tag('unseen')\r
230                 msg.remove_tag('new')\r
231 \r
232 # Remove new from sorted list items\r
233 tag_search(db, 'tag:new and tag:list', '-new')\r
234 \r
235 # Tag remaining new items for inbox\r
236 tag_search(db, 'tag:new', '+inbox', '-new')\r
237 \r
238 # Thaw new messages\r
239 for msg in q_new.search_messages():\r
240         msg.thaw()\r
241 \r
242 end_time = time.time()\r
243 logging.info('Sorted %d messages in %1.2f seconds' % (n_msgs, end_time - start_time))\r
244 \r
245 ============================================================\r
246 \r
247 \r
248 ===File ~/.env/mail/sort_junk.py============================\r
249 #!/usr/bin/python\r
250 \r
251 import logging\r
252 import subprocess\r
253 from subprocess import PIPE\r
254 import notmuch\r
255 import re\r
256 \r
257 def sort_junk(query):\r
258         spam_re = re.compile('X-Bogosity:\s*Spam')\r
259         spamicity_re = re.compile('spamicity=(\d\.\d+)')\r
260         bf = subprocess.Popen(['bogofilter', '-bv'], stdin=PIPE, stdout=PIPE)\r
261         for msg in query.search_messages():\r
262                 bf.stdin.write(msg.get_filename() + '\n')\r
263                 l = bf.stdout.readline()\r
264                 if re.search(spam_re, l):\r
265                         logging.debug('Message %s marked as junk' % msg.get_message_id())\r
266                         msg.add_tag('junk')\r
267         bf.stdin.close()\r
268 \r
269 if __name__ == '__main__':\r
270         import sys\r
271         db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)\r
272         query = notmuch.Query(db, ' '.join(sys.argv[1:]))\r
273         sort_junk(query)\r
274 \r
275 ============================================================\r
276 \r
277 \r
278 ===File ~/.env/mail/update-junk=============================\r
279 #!/usr/bin/python\r
280 \r
281 import notmuch\r
282 from notmuch_utils import *\r
283 import subprocess\r
284 from time import time\r
285 import sys\r
286 \r
287 logging.basicConfig(level=logging.INFO)\r
288 \r
289 db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)\r
290 if '--clean' in sys.argv:\r
291         import shutil, os.path\r
292         shutil.rmtree(os.path.expanduser('~/.bogofilter'))\r
293         tag_search(db, 'tag:.bf_spam', '-.bf_spam')\r
294         tag_search(db, 'tag:.bf_ham', '-.bf_ham')\r
295 \r
296 def do_update(search, tag_func, bf_args):\r
297         start_time = time()\r
298         p = subprocess.Popen(['bogofilter', bf_args], stdin=subprocess.PIPE)\r
299         q = notmuch.Query(db, search)\r
300         n = 0\r
301         for msg in q.search_messages():\r
302                 p.stdin.write('%s\n' % msg.get_filename())\r
303                 tag_func(msg)\r
304                 n += 1\r
305         p.stdin.close()\r
306         p.wait()\r
307         return (n, time()-start_time)\r
308 \r
309 logging.info('Registering spam')\r
310 n,t = do_update('tag:junk and not tag:.bf_spam', lambda msg: msg.add_tag('.bf_spam'), '-sb')\r
311 logging.info('Registered %d spam in %1.2f seconds' % (n,t))\r
312 \r
313 logging.info('Unregistering spam')\r
314 n,t = do_update('not tag:junk and tag:.bf_spam', lambda msg: msg.remove_tag('.bf_spam'), '-Sb')\r
315 logging.info('Unregistered %d spam in %1.2f seconds' % (n,t))\r
316 \r
317 # Only consider messages that have been read as ham\r
318 logging.info('Registering ham')\r
319 n,t = do_update('not tag:junk and not tag:unread and not tag:.bf_ham', lambda msg: msg.add_tag('.bf_ham'), '-nb')\r
320 logging.info('Registered %d ham in %1.2f seconds' % (n,t))\r
321 \r
322 logging.info('Unregistering ham')\r
323 n,t = do_update('tag:junk and tag:.bf_ham', lambda msg: msg.remove_tag('.bf_ham'), '-Nb')\r
324 logging.info('Unregistered %d ham in %1.2f seconds' % (n,t))\r
325 \r
326 ============================================================\r