* wl-spam.el (wl-summary-exec-action-refile-with-register): Specify "spam" or "good".
[elisp/wanderlust.git] / wl / wl-spam.el
1 ;;; wl-spam.el --- Spam filtering interface for Wanderlust.
2
3 ;; Copyright (C) 2003 Hiroya Murata <lapis-lazuli@pop06.odn.ne.jp>
4 ;; Copyright (C) 2003 Yuuichi Teranishi <teranisi@gohome.org>
5
6 ;; Author: Hiroya Murata <lapis-lazuli@pop06.odn.ne.jp>
7 ;; Keywords: mail, net news, spam
8
9 ;; This file is part of Wanderlust (Yet Another Message Interface on Emacsen).
10
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.
15 ;;
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.
20 ;;
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.
25 ;;
26
27 ;;; Commentary:
28 ;;
29
30 ;;; Code:
31 ;;
32
33 (eval-when-compile (require 'cl))
34
35 (require 'elmo-spam)
36 (require 'wl-summary)
37 (require 'wl-action)
38 (require 'wl-highlight)
39
40 (defgroup wl-spam nil
41   "Spam configuration for wanderlust."
42   :group 'wl)
43
44 (defcustom wl-spam-folder "+spam"
45   "*Spam folder."
46   :type 'string
47   :group 'wl-spam)
48
49 (defcustom wl-spam-undecided-folder-regexp-list '("inbox")
50   "*List of folder regexp which is contained undecided domain."
51   :type '(repeat (regexp :tag "Folder Regexp"))
52   :group 'wl-spam)
53
54 (defcustom wl-spam-ignored-folder-regexp-list
55   (list (regexp-opt (list wl-draft-folder
56                           wl-trash-folder
57                           wl-queue-folder)))
58   "*List of folder regexp which is contained ignored domain."
59   :type '(repeat (regexp :tag "Folder Regexp"))
60   :group 'wl-spam)
61
62 (defcustom wl-spam-auto-check-folder-regexp-list nil
63   "*List of Folder regexp which check spam automatically."
64   :type '(repeat (regexp :tag "Folder Regexp"))
65   :group 'wl-spam)
66
67 (defcustom wl-spam-auto-check-marks
68   (list wl-summary-new-uncached-mark
69         wl-summary-new-cached-mark)
70   "Persistent marks to check spam automatically."
71   :type '(choice (const :tag "All marks" all)
72                  (repeat (string :tag "Mark")))
73   :group 'wl-spam)
74
75 (wl-defface wl-highlight-summary-spam-face
76   '((((type tty)
77       (background dark))
78      (:foreground "blue"))
79     (((class color))
80      (:foreground "LightSlateGray")))
81   "Face used for displaying messages mark as spam."
82   :group 'wl-summary-faces
83   :group 'wl-faces)
84
85 (defcustom wl-spam-mark-action-list
86   '(("s"
87      spam
88      nil
89      wl-summary-register-temp-mark
90      wl-summary-exec-action-spam
91      wl-highlight-summary-spam-face
92      "Mark messages as spam."))
93   "A variable to define Mark & Action for spam.
94 Append this value to `wl-summary-mark-action-list' by `wl-spam-setup'.
95
96 See `wl-summary-mark-action-list' for the detail of element."
97   :type '(repeat (string :tag "Temporary mark")
98                  (symbol :tag "Set mark function")
99                  (symbol :tag "Unset mark function")
100                  (symbol :tag "Exec function")
101                  (symbol :tag "Face symbol")
102                  (string :tag "Document string"))
103   :group 'wl-spam)
104
105 (defun wl-spam-domain (folder-name)
106   (cond ((string= folder-name wl-spam-folder)
107          'spam)
108         ((wl-string-match-member folder-name
109                                  wl-spam-undecided-folder-regexp-list)
110          'undecided)
111         ((wl-string-match-member folder-name
112                                  wl-spam-ignored-folder-regexp-list)
113          'ignore)
114         (t
115          'good)))
116
117 (defsubst wl-spam-auto-check-message-p (folder number)
118   (or (eq wl-spam-auto-check-marks 'all)
119       (member (wl-summary-message-mark folder number)
120               wl-spam-auto-check-marks)))
121
122 (defsubst wl-spam-map-spam-messages (folder numbers function &rest args)
123   (let ((total (length numbers)))
124     (message "Checking spam...")
125     (elmo-with-progress-display (> total elmo-display-progress-threshold)
126         (elmo-spam-check-spam total "Checking spam...")
127       (dolist (number (elmo-spam-list-spam-messages (elmo-spam-processor)
128                                                     folder
129                                                     numbers))
130         (apply function number args)))
131     (message "Checking spam...done")))
132
133 (defun wl-spam-register-spam-messages (folder numbers)
134   (let ((total (length numbers)))
135     (message "Registering spam...")
136     (elmo-with-progress-display (> total elmo-display-progress-threshold)
137         (elmo-spam-register total "Registering spam...")
138       (elmo-spam-register-spam-messages (elmo-spam-processor)
139                                         wl-summary-buffer-elmo-folder
140                                         numbers))
141     (message "Registering spam...done")))
142
143 (defun wl-spam-register-good-messages (folder numbers)
144   (let ((total (length numbers)))
145     (message "Registering good...")
146     (elmo-with-progress-display (> total elmo-display-progress-threshold)
147         (elmo-spam-register total "Registering good...")
148       (elmo-spam-register-good-messages (elmo-spam-processor)
149                                         wl-summary-buffer-elmo-folder
150                                         numbers))
151     (message "Registering good...done")))
152
153 (defun wl-spam-save-status (&optional force)
154   (interactive "P")
155   (let ((processor (elmo-spam-processor (not force))))
156     (when (or force
157               (and processor (elmo-spam-modified-p processor)))
158       (elmo-spam-save-status processor))))
159
160 ;; insinuate into summary mode
161 (defvar wl-summary-spam-map nil)
162
163 (unless wl-summary-spam-map
164   (let ((map (make-sparse-keymap)))
165     (define-key map "m" 'wl-summary-spam)
166     (define-key map "c" 'wl-summary-test-spam)
167     (define-key map "C" 'wl-summary-mark-spam)
168     (define-key map "s" 'wl-summary-register-as-spam)
169     (define-key map "S" 'wl-summary-register-as-spam-all)
170     (define-key map "n" 'wl-summary-register-as-good)
171     (define-key map "N" 'wl-summary-register-as-good-all)
172     (setq wl-summary-spam-map map)))
173
174 (eval-when-compile
175   ;; Avoid compile warnings
176   (defalias-maybe 'wl-summary-spam 'ignore))
177
178 (defun wl-summary-test-spam (&optional folder number)
179   (interactive)
180   (let ((folder (or folder wl-summary-buffer-elmo-folder))
181         (number (or number (wl-summary-message-number)))
182         spam)
183     (message "Cheking spam...")
184     (when (setq spam (elmo-spam-message-spam-p (elmo-spam-processor)
185                                                folder number))
186       (wl-summary-spam number))
187     (message "Cheking spam...done")
188     (when (interactive-p)
189       (message "No: %d is %sa spam message." number (if spam "" "not ")))))
190
191 (defun wl-summary-mark-spam (&optional all)
192   "Set spam mark to messages which is spam classification."
193   (interactive "P")
194   (let (numbers)
195     (if all
196         (setq numbers wl-summary-buffer-number-list)
197       (dolist (number wl-summary-buffer-number-list)
198         (when (wl-spam-auto-check-message-p wl-summary-buffer-elmo-folder
199                                             number)
200           (setq numbers (cons number numbers)))))
201     (cond (numbers
202            (wl-spam-map-spam-messages wl-summary-buffer-elmo-folder
203                                       numbers
204                                       #'wl-summary-spam))
205           ((interactive-p)
206            (message "No message to test.")))))
207
208 (defun wl-summary-register-as-spam ()
209   (interactive)
210   (let ((number (wl-summary-message-number)))
211     (when number
212       (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
213                                       (list number)))))
214
215 (defun wl-summary-register-as-spam-all ()
216   (interactive)
217   (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
218                                   wl-summary-buffer-number-list))
219
220 (defun wl-summary-target-mark-register-as-spam ()
221   (interactive)
222   (save-excursion
223     (goto-char (point-min))
224     (let ((inhibit-read-only t)
225           (buffer-read-only nil)
226           wl-summary-buffer-disp-msg)
227       (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
228                                       wl-summary-buffer-target-mark-list)
229       (dolist (number wl-summary-buffer-target-mark-list)
230         (wl-summary-unset-mark number)))))
231
232 (defun wl-summary-register-as-good ()
233   (interactive)
234   (let ((number (wl-summary-message-number)))
235     (when number
236       (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
237                                       (list number)))))
238
239 (defun wl-summary-register-as-good-all ()
240   (interactive)
241   (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
242                                   wl-summary-buffer-number-list))
243
244 (defun wl-summary-target-mark-register-as-good ()
245   (interactive)
246   (save-excursion
247     (goto-char (point-min))
248     (let ((inhibit-read-only t)
249           (buffer-read-only nil)
250           wl-summary-buffer-disp-msg)
251       (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
252                                       wl-summary-buffer-target-mark-list)
253       (dolist (number wl-summary-buffer-target-mark-list)
254         (wl-summary-unset-mark number)))))
255
256 ;; hook functions and other
257 (defun wl-summary-auto-check-spam ()
258   (when (elmo-string-match-member (wl-summary-buffer-folder-name)
259                                   wl-spam-auto-check-folder-regexp-list)
260     (wl-summary-mark-spam)))
261
262 (defun wl-summary-exec-action-spam (mark-list)
263   (let ((domain (wl-spam-domain (elmo-folder-name-internal
264                                  wl-summary-buffer-elmo-folder)))
265         (total (length mark-list)))
266     (wl-folder-confirm-existence (elmo-make-folder wl-spam-folder))
267     (when (memq domain '(undecided good))
268       (message "Registering spam...")
269       (elmo-with-progress-display (> total elmo-display-progress-threshold)
270           (elmo-spam-register total "Registering spam...")
271         (elmo-spam-register-spam-messages (elmo-spam-processor)
272                                           wl-summary-buffer-elmo-folder
273                                           (mapcar #'car mark-list)
274                                           (eq domain 'good)))
275       (message "Registering spam...done"))
276     (wl-summary-move-mark-list-messages mark-list
277                                         wl-spam-folder
278                                         "Refiling spam...")))
279
280 (defun wl-summary-exec-action-refile-with-register (mark-list)
281   (let* ((processor (elmo-spam-processor))
282          (folder wl-summary-buffer-elmo-folder)
283          (domain (wl-spam-domain (elmo-folder-name-internal folder)))
284          spam-list good-list total)
285     (unless (eq domain 'ignore)
286       (dolist (info mark-list)
287         (case (wl-spam-domain (nth 2 info))
288           (spam
289            (setq spam-list (cons (car info) spam-list)))
290           (good
291            (setq good-list (cons (car info) good-list)))))
292       (case domain
293         (spam (setq spam-list nil))
294         (good (setq good-list nil)))
295       (when (or spam-list good-list)
296         (setq total (+ (length spam-list) (length good-list)))
297         (when spam-list
298           (message "Registering spam...")
299           (elmo-with-progress-display (> total elmo-display-progress-threshold)
300               (elmo-spam-register total "Registering spam...")
301             (elmo-spam-register-spam-messages processor folder spam-list
302                                               (eq domain 'good)))
303           (message "Registering spam...done"))
304         (when good-list
305           (message "Registering good...")
306           (elmo-with-progress-display (> total elmo-display-progress-threshold)
307               (elmo-spam-register total "Registering good...")
308             (elmo-spam-register-good-messages processor folder good-list
309                                               (eq domain 'spam)))
310           (message "Registering good...done"))))
311     ;; execute refile messages
312     (wl-summary-exec-action-refile mark-list)))
313
314 (defun wl-message-check-spam ()
315   (let ((original (wl-message-get-original-buffer))
316         (number wl-message-buffer-cur-number)
317         spam)
318     (message "Cheking spam...")
319     (when (elmo-spam-buffer-spam-p (elmo-spam-processor) original)
320       (with-current-buffer wl-message-buffer-cur-summary-buffer
321         (wl-summary-spam number)))
322     (message "Cheking spam...done")
323     (when (interactive-p)
324       (message "No: %d is %sa spam message." number (if spam "" "not ")))))
325
326 (defun wl-refile-guess-by-spam (entity)
327   (when (elmo-spam-message-spam-p (elmo-spam-processor)
328                                   wl-summary-buffer-elmo-folder
329                                   (elmo-message-entity-number entity))
330     wl-spam-folder))
331
332 (defun wl-spam-setup ()
333   (add-hook 'wl-summary-prepared-hook #'wl-summary-auto-check-spam)
334   (let ((actions wl-summary-mark-action-list)
335         action)
336     (while actions
337       (setq action  (car actions)
338             actions (cdr actions))
339       (when (eq (wl-summary-action-symbol action) 'refile)
340         (setcar (nthcdr 4 action) 'wl-summary-exec-action-refile-with-register)
341         (setq actions nil))))
342   (when wl-spam-mark-action-list
343     (setq wl-summary-mark-action-list (append
344                                        wl-summary-mark-action-list
345                                        wl-spam-mark-action-list))
346     (dolist (action wl-spam-mark-action-list)
347       (setq wl-summary-reserve-mark-list
348             (cons (wl-summary-action-mark action)
349                   wl-summary-reserve-mark-list))
350       (setq wl-summary-skip-mark-list
351             (cons (wl-summary-action-mark action)
352                   wl-summary-skip-mark-list))))
353   (define-key wl-summary-mode-map "k" wl-summary-spam-map)
354   (define-key
355     wl-summary-mode-map "mk" 'wl-summary-target-mark-spam)
356   (define-key
357     wl-summary-mode-map "ms" 'wl-summary-target-mark-register-as-spam)
358   (define-key
359     wl-summary-mode-map "mn" 'wl-summary-target-mark-register-as-good))
360
361 (require 'product)
362 (product-provide (provide 'wl-spam) (require 'wl-version))
363
364 (unless noninteractive
365   (wl-spam-setup))
366
367 ;;; wl-spam.el ends here