New feature: toggle visibility of mime buttons.
[more-wl.git] / wl / wl-spam.el
blob8c94de0d2eb67c7a4f90eabeb0d68d3d1eba6632
1 ;;; wl-spam.el --- Spam filtering interface for Wanderlust.
3 ;; Copyright (C) 2003 Hiroya Murata <lapis-lazuli@pop06.odn.ne.jp>
4 ;; Copyright (C) 2003 Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Author: Hiroya Murata <lapis-lazuli@pop06.odn.ne.jp>
7 ;; Keywords: mail, net news, spam
9 ;; This file is part of Wanderlust (Yet Another Message Interface on Emacsen).
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
27 ;;; Commentary:
30 ;;; Code:
33 (eval-when-compile (require 'cl))
35 (require 'elmo-spam)
36 (require 'wl-summary)
37 (require 'wl-action)
38 (require 'wl-highlight)
40 (defgroup wl-spam nil
41 "Spam configuration for wanderlust."
42 :group 'wl)
44 (defcustom wl-spam-folder "+spam"
45 "*Spam folder."
46 :type 'string
47 :group 'wl-spam)
49 (defcustom wl-spam-undecided-folder-list nil
50 "*List of folder name which is contained undecided domain.
51 If an element is symbol, use symbol-value instead."
52 :type '(repeat (choice (string :tag "Folder name")
53 (variable :tag "Variable")))
54 :group 'wl-spam)
56 (defcustom wl-spam-undecided-folder-regexp-list '("inbox")
57 "*List of folder regexp which is contained undecided domain."
58 :type '(repeat (regexp :tag "Folder Regexp"))
59 :group 'wl-spam)
61 (defcustom wl-spam-ignored-folder-list '(wl-draft-folder
62 wl-trash-folder
63 wl-queue-folder)
64 "*List of folder name which is contained ignored domain.
65 If an element is symbol, use symbol-value instead."
66 :type '(repeat (choice (string :tag "Folder name")
67 (variable :tag "Variable")))
68 :group 'wl-spam)
70 (defcustom wl-spam-ignored-folder-regexp-list nil
71 "*List of folder regexp which is contained ignored domain."
72 :type '(repeat (regexp :tag "Folder Regexp"))
73 :group 'wl-spam)
75 (defcustom wl-spam-auto-check-folder-regexp-list nil
76 "*List of Folder regexp which check spam automatically."
77 :type '(repeat (regexp :tag "Folder Regexp"))
78 :group 'wl-spam)
80 (defcustom wl-spam-auto-check-marks
81 (list wl-summary-new-uncached-mark
82 wl-summary-new-cached-mark)
83 "Persistent marks to check spam automatically."
84 :type '(choice (const :tag "All marks" all)
85 (repeat (string :tag "Mark")))
86 :group 'wl-spam)
88 (wl-defface wl-highlight-summary-spam-face
89 '((((type tty)
90 (background dark))
91 (:foreground "blue"))
92 (((class color))
93 (:foreground "LightSlateGray")))
94 "Face used for displaying messages mark as spam."
95 :group 'wl-summary-faces
96 :group 'wl-faces)
98 (defcustom wl-spam-mark-action-list
99 '(("s"
100 spam
102 wl-summary-register-temp-mark
103 wl-summary-exec-action-spam
104 wl-highlight-summary-spam-face
105 "Mark messages as spam."))
106 "A variable to define Mark & Action for spam.
107 Append this value to `wl-summary-mark-action-list' by `wl-spam-setup'.
109 See `wl-summary-mark-action-list' for the detail of element."
110 :type '(repeat (list
111 (string :tag "Temporary mark")
112 (symbol :tag "Action name")
113 (symbol :tag "Argument function")
114 (symbol :tag "Set mark function")
115 (symbol :tag "Exec function")
116 (symbol :tag "Face symbol")
117 (string :tag "Document string")))
118 :group 'wl-spam)
120 (defsubst wl-spam-string-member-p (string list regexp-list)
121 (or (wl-string-member string list)
122 (wl-string-match-member string regexp-list)))
124 (defun wl-spam-domain (folder-name)
125 (cond ((string= folder-name wl-spam-folder)
126 'spam)
127 ((wl-spam-string-member-p folder-name
128 wl-spam-undecided-folder-list
129 wl-spam-undecided-folder-regexp-list)
130 'undecided)
131 ((wl-spam-string-member-p folder-name
132 wl-spam-ignored-folder-list
133 wl-spam-ignored-folder-regexp-list)
134 'ignore)
136 'good)))
138 (defun wl-spam-split-numbers (folder numbers)
139 (let (alist)
140 (dolist (number numbers)
141 (let* ((domain (wl-spam-domain
142 (elmo-folder-name-internal
143 (elmo-message-folder folder number))))
144 (cell (assq domain alist)))
145 (if cell
146 (setcdr cell (cons number (cdr cell)))
147 (setq alist (cons (list domain number) alist)))))
148 alist))
150 (defsubst wl-spam-auto-check-message-p (folder number)
151 (or (eq wl-spam-auto-check-marks 'all)
152 (member (wl-summary-message-mark folder number)
153 wl-spam-auto-check-marks)))
155 (defsubst wl-spam-map-spam-messages (folder numbers function &rest args)
156 (elmo-with-progress-display (elmo-spam-check-spam (length numbers))
157 "Checking spam"
158 (dolist (number (elmo-spam-list-spam-messages (elmo-spam-processor)
159 folder
160 numbers))
161 (apply function number args))))
163 (defun wl-spam-apply-partitions (folder partitions function msg)
164 (when partitions
165 (let ((total 0))
166 (dolist (partition partitions)
167 (setq total (+ total (length (cdr partition)))))
168 (elmo-with-progress-display (elmo-spam-register total) msg
169 (dolist (partition partitions)
170 (funcall function folder (cdr partition) (car partition)))))))
172 (defun wl-spam-register-spam-messages (folder numbers)
173 (elmo-with-progress-display (elmo-spam-register (length numbers))
174 "Registering spam"
175 (elmo-spam-register-spam-messages (elmo-spam-processor)
176 folder
177 numbers)))
179 (defun wl-spam-register-good-messages (folder numbers)
180 (elmo-with-progress-display (elmo-spam-register (length numbers))
181 "Registering good"
182 (elmo-spam-register-good-messages (elmo-spam-processor)
183 folder
184 numbers)))
186 (defun wl-spam-save-status (&optional force)
187 (interactive "P")
188 (let ((processor (elmo-spam-processor (not force))))
189 (when (or force
190 (and processor (elmo-spam-modified-p processor)))
191 (elmo-spam-save-status processor))))
193 ;; insinuate into summary mode
194 (defvar wl-summary-spam-map nil)
196 (unless wl-summary-spam-map
197 (let ((map (make-sparse-keymap)))
198 (define-key map "m" 'wl-summary-spam)
199 (define-key map "c" 'wl-summary-test-spam)
200 (define-key map "C" 'wl-summary-mark-spam)
201 (define-key map "s" 'wl-summary-register-as-spam)
202 (define-key map "S" 'wl-summary-register-as-spam-all)
203 (define-key map "n" 'wl-summary-register-as-good)
204 (define-key map "N" 'wl-summary-register-as-good-all)
205 (setq wl-summary-spam-map map)))
207 (eval-when-compile
208 ;; Avoid compile warnings
209 (defalias-maybe 'wl-summary-spam 'ignore))
211 (defun wl-summary-test-spam (&optional folder number)
212 (interactive)
213 (let ((folder (or folder wl-summary-buffer-elmo-folder))
214 (number (or number (wl-summary-message-number)))
215 spam)
216 (message "Checking spam...")
217 (when (setq spam (elmo-spam-message-spam-p (elmo-spam-processor)
218 folder number))
219 (wl-summary-spam number))
220 (message "Checking spam...done")
221 (when (interactive-p)
222 (message "No: %d is %sa spam message." number (if spam "" "not ")))))
224 (defun wl-summary-test-spam-region (beg end)
225 (interactive "r")
226 (let ((numbers (wl-summary-collect-numbers-region beg end)))
227 (cond (numbers
228 (wl-spam-map-spam-messages wl-summary-buffer-elmo-folder
229 numbers
230 #'wl-summary-spam))
231 ((interactive-p)
232 (message "No message to test.")))))
234 (defun wl-thread-test-spam (&optional arg)
235 (interactive "P")
236 (wl-thread-call-region-func 'wl-summary-test-spam-region arg))
238 (defun wl-summary-mark-spam (&optional all)
239 "Set spam mark to messages which is spam classification."
240 (interactive "P")
241 (let (numbers)
242 (if all
243 (setq numbers wl-summary-buffer-number-list)
244 (dolist (number wl-summary-buffer-number-list)
245 (when (wl-spam-auto-check-message-p wl-summary-buffer-elmo-folder
246 number)
247 (setq numbers (cons number numbers)))))
248 (cond (numbers
249 (wl-spam-map-spam-messages wl-summary-buffer-elmo-folder
250 numbers
251 #'wl-summary-spam))
252 ((interactive-p)
253 (message "No message to test.")))))
255 (defun wl-summary-register-as-spam ()
256 (interactive)
257 (let ((number (wl-summary-message-number)))
258 (when number
259 (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
260 (list number)))))
262 (defun wl-summary-register-as-spam-all ()
263 (interactive)
264 (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
265 wl-summary-buffer-number-list))
267 (defun wl-summary-target-mark-register-as-spam ()
268 (interactive)
269 (save-excursion
270 (goto-char (point-min))
271 (let ((inhibit-read-only t)
272 (buffer-read-only nil)
273 wl-summary-buffer-disp-msg)
274 (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
275 wl-summary-buffer-target-mark-list)
276 (dolist (number wl-summary-buffer-target-mark-list)
277 (wl-summary-unset-mark number)))))
279 (defun wl-summary-register-as-good ()
280 (interactive)
281 (let ((number (wl-summary-message-number)))
282 (when number
283 (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
284 (list number)))))
286 (defun wl-summary-register-as-good-all ()
287 (interactive)
288 (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
289 wl-summary-buffer-number-list))
291 (defun wl-summary-target-mark-register-as-good ()
292 (interactive)
293 (save-excursion
294 (goto-char (point-min))
295 (let ((inhibit-read-only t)
296 (buffer-read-only nil)
297 wl-summary-buffer-disp-msg)
298 (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
299 wl-summary-buffer-target-mark-list)
300 (dolist (number wl-summary-buffer-target-mark-list)
301 (wl-summary-unset-mark number)))))
303 ;; hook functions and other
304 (defun wl-summary-auto-check-spam ()
305 (when (elmo-string-match-member (wl-summary-buffer-folder-name)
306 wl-spam-auto-check-folder-regexp-list)
307 (wl-summary-mark-spam)))
309 (defun wl-summary-exec-action-spam (mark-list)
310 (let ((folder wl-summary-buffer-elmo-folder))
311 (wl-folder-confirm-existence (wl-folder-get-elmo-folder wl-spam-folder))
312 (wl-spam-apply-partitions
313 folder
314 (wl-filter-associations
315 '(undecided good)
316 (wl-spam-split-numbers folder (mapcar #'car mark-list)))
317 (lambda (folder numbers domain)
318 (elmo-spam-register-spam-messages (elmo-spam-processor)
319 folder numbers
320 (eq domain 'good)))
321 "Registering spam")
322 (wl-summary-move-mark-list-messages mark-list
323 wl-spam-folder
324 "Refiling spam")))
326 (defun wl-summary-exec-action-refile-with-register (mark-list)
327 (let ((folder wl-summary-buffer-elmo-folder)
328 spam-list good-list)
329 (dolist (info mark-list)
330 (case (wl-spam-domain (nth 2 info))
331 (spam
332 (setq spam-list (cons (car info) spam-list)))
333 (good
334 (setq good-list (cons (car info) good-list)))))
335 (wl-spam-apply-partitions
336 folder
337 (wl-filter-associations '(undecided good)
338 (wl-spam-split-numbers folder spam-list))
339 (lambda (folder numbers domain)
340 (elmo-spam-register-spam-messages (elmo-spam-processor)
341 folder numbers
342 (eq domain 'good)))
343 "Registering spam")
344 (wl-spam-apply-partitions
345 folder
346 (wl-filter-associations '(undecided spam)
347 (wl-spam-split-numbers folder good-list))
348 (lambda (folder numbers domain)
349 (elmo-spam-register-good-messages (elmo-spam-processor)
350 folder numbers
351 (eq domain 'spam)))
352 "Registering good")
353 ;; execute refile messages
354 (wl-summary-exec-action-refile mark-list)))
356 (defun wl-message-check-spam ()
357 (let ((original (wl-message-get-original-buffer))
358 (number wl-message-buffer-cur-number)
359 spam)
360 (message "Checking spam...")
361 (when (setq spam (elmo-spam-buffer-spam-p (elmo-spam-processor) original))
362 (with-current-buffer wl-message-buffer-cur-summary-buffer
363 (wl-summary-spam number)))
364 (message "Checking spam...done")
365 (message "No: %d is %sa spam message." number (if spam "" "not "))))
367 (defun wl-refile-guess-by-spam (entity)
368 (when (elmo-spam-message-spam-p (elmo-spam-processor)
369 wl-summary-buffer-elmo-folder
370 (elmo-message-entity-number entity))
371 wl-spam-folder))
373 (defun wl-spam-setup ()
374 (add-hook 'wl-summary-sync-updated-hook #'wl-summary-auto-check-spam)
375 (let ((actions wl-summary-mark-action-list)
376 action)
377 (while actions
378 (setq action (car actions)
379 actions (cdr actions))
380 (when (eq (wl-summary-action-symbol action) 'refile)
381 (setcar (nthcdr 4 action) 'wl-summary-exec-action-refile-with-register)
382 (setq actions nil))))
383 (when wl-spam-mark-action-list
384 (setq wl-summary-mark-action-list (append
385 wl-summary-mark-action-list
386 wl-spam-mark-action-list))
387 (dolist (action wl-spam-mark-action-list)
388 (setq wl-summary-reserve-mark-list
389 (cons (wl-summary-action-mark action)
390 wl-summary-reserve-mark-list))
391 (setq wl-summary-skip-mark-list
392 (cons (wl-summary-action-mark action)
393 wl-summary-skip-mark-list))))
394 (define-key wl-summary-mode-map "k" wl-summary-spam-map)
395 (define-key
396 wl-summary-mode-map "rkm" 'wl-summary-spam-region)
397 (define-key
398 wl-summary-mode-map "rkc" 'wl-summary-test-spam-region)
399 (define-key
400 wl-summary-mode-map "tkm" 'wl-thread-spam)
401 (define-key
402 wl-summary-mode-map "tkc" 'wl-thread-test-spam)
403 (define-key
404 wl-summary-mode-map "mk" 'wl-summary-target-mark-spam)
405 (define-key
406 wl-summary-mode-map "ms" 'wl-summary-target-mark-register-as-spam)
407 (define-key
408 wl-summary-mode-map "mn" 'wl-summary-target-mark-register-as-good))
410 (require 'product)
411 (product-provide (provide 'wl-spam) (require 'wl-version))
413 (unless noninteractive
414 (wl-spam-setup))
416 ;;; wl-spam.el ends here