* wl-vars.el (wl-message-use-header-narrowing): New user option.
[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 (list
98                   (string :tag "Temporary mark")
99                   (symbol :tag "Action name")
100                   (symbol :tag "Argument function")
101                   (symbol :tag "Set mark function")
102                   (symbol :tag "Exec function")
103                   (symbol :tag "Face symbol")
104                   (string :tag "Document string")))
105   :group 'wl-spam)
106
107 (defun wl-spam-domain (folder-name)
108   (cond ((string= folder-name wl-spam-folder)
109          'spam)
110         ((wl-string-match-member folder-name
111                                  wl-spam-undecided-folder-regexp-list)
112          'undecided)
113         ((wl-string-match-member folder-name
114                                  wl-spam-ignored-folder-regexp-list)
115          'ignore)
116         (t
117          'good)))
118
119 (defsubst wl-spam-auto-check-message-p (folder number)
120   (or (eq wl-spam-auto-check-marks 'all)
121       (member (wl-summary-message-mark folder number)
122               wl-spam-auto-check-marks)))
123
124 (defsubst wl-spam-map-spam-messages (folder numbers function &rest args)
125   (let ((total (length numbers)))
126     (message "Checking spam...")
127     (elmo-with-progress-display (> total elmo-display-progress-threshold)
128         (elmo-spam-check-spam total "Checking spam...")
129       (dolist (number (elmo-spam-list-spam-messages (elmo-spam-processor)
130                                                     folder
131                                                     numbers))
132         (apply function number args)))
133     (message "Checking spam...done")))
134
135 (defun wl-spam-register-spam-messages (folder numbers)
136   (let ((total (length numbers)))
137     (message "Registering spam...")
138     (elmo-with-progress-display (> total elmo-display-progress-threshold)
139         (elmo-spam-register total "Registering spam...")
140       (elmo-spam-register-spam-messages (elmo-spam-processor)
141                                         wl-summary-buffer-elmo-folder
142                                         numbers))
143     (message "Registering spam...done")))
144
145 (defun wl-spam-register-good-messages (folder numbers)
146   (let ((total (length numbers)))
147     (message "Registering good...")
148     (elmo-with-progress-display (> total elmo-display-progress-threshold)
149         (elmo-spam-register total "Registering good...")
150       (elmo-spam-register-good-messages (elmo-spam-processor)
151                                         wl-summary-buffer-elmo-folder
152                                         numbers))
153     (message "Registering good...done")))
154
155 (defun wl-spam-save-status (&optional force)
156   (interactive "P")
157   (let ((processor (elmo-spam-processor (not force))))
158     (when (or force
159               (and processor (elmo-spam-modified-p processor)))
160       (elmo-spam-save-status processor))))
161
162 ;; insinuate into summary mode
163 (defvar wl-summary-spam-map nil)
164
165 (unless wl-summary-spam-map
166   (let ((map (make-sparse-keymap)))
167     (define-key map "m" 'wl-summary-spam)
168     (define-key map "c" 'wl-summary-test-spam)
169     (define-key map "C" 'wl-summary-mark-spam)
170     (define-key map "s" 'wl-summary-register-as-spam)
171     (define-key map "S" 'wl-summary-register-as-spam-all)
172     (define-key map "n" 'wl-summary-register-as-good)
173     (define-key map "N" 'wl-summary-register-as-good-all)
174     (setq wl-summary-spam-map map)))
175
176 (eval-when-compile
177   ;; Avoid compile warnings
178   (defalias-maybe 'wl-summary-spam 'ignore))
179
180 (defun wl-summary-test-spam (&optional folder number)
181   (interactive)
182   (let ((folder (or folder wl-summary-buffer-elmo-folder))
183         (number (or number (wl-summary-message-number)))
184         spam)
185     (message "Cheking spam...")
186     (when (setq spam (elmo-spam-message-spam-p (elmo-spam-processor)
187                                                folder number))
188       (wl-summary-spam number))
189     (message "Cheking spam...done")
190     (when (interactive-p)
191       (message "No: %d is %sa spam message." number (if spam "" "not ")))))
192
193 (defun wl-summary-mark-spam (&optional all)
194   "Set spam mark to messages which is spam classification."
195   (interactive "P")
196   (let (numbers)
197     (if all
198         (setq numbers wl-summary-buffer-number-list)
199       (dolist (number wl-summary-buffer-number-list)
200         (when (wl-spam-auto-check-message-p wl-summary-buffer-elmo-folder
201                                             number)
202           (setq numbers (cons number numbers)))))
203     (cond (numbers
204            (wl-spam-map-spam-messages wl-summary-buffer-elmo-folder
205                                       numbers
206                                       #'wl-summary-spam))
207           ((interactive-p)
208            (message "No message to test.")))))
209
210 (defun wl-summary-register-as-spam ()
211   (interactive)
212   (let ((number (wl-summary-message-number)))
213     (when number
214       (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
215                                       (list number)))))
216
217 (defun wl-summary-register-as-spam-all ()
218   (interactive)
219   (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
220                                   wl-summary-buffer-number-list))
221
222 (defun wl-summary-target-mark-register-as-spam ()
223   (interactive)
224   (save-excursion
225     (goto-char (point-min))
226     (let ((inhibit-read-only t)
227           (buffer-read-only nil)
228           wl-summary-buffer-disp-msg)
229       (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
230                                       wl-summary-buffer-target-mark-list)
231       (dolist (number wl-summary-buffer-target-mark-list)
232         (wl-summary-unset-mark number)))))
233
234 (defun wl-summary-register-as-good ()
235   (interactive)
236   (let ((number (wl-summary-message-number)))
237     (when number
238       (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
239                                       (list number)))))
240
241 (defun wl-summary-register-as-good-all ()
242   (interactive)
243   (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
244                                   wl-summary-buffer-number-list))
245
246 (defun wl-summary-target-mark-register-as-good ()
247   (interactive)
248   (save-excursion
249     (goto-char (point-min))
250     (let ((inhibit-read-only t)
251           (buffer-read-only nil)
252           wl-summary-buffer-disp-msg)
253       (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
254                                       wl-summary-buffer-target-mark-list)
255       (dolist (number wl-summary-buffer-target-mark-list)
256         (wl-summary-unset-mark number)))))
257
258 ;; hook functions and other
259 (defun wl-summary-auto-check-spam ()
260   (when (elmo-string-match-member (wl-summary-buffer-folder-name)
261                                   wl-spam-auto-check-folder-regexp-list)
262     (wl-summary-mark-spam)))
263
264 (defun wl-summary-exec-action-spam (mark-list)
265   (let ((domain (wl-spam-domain (elmo-folder-name-internal
266                                  wl-summary-buffer-elmo-folder)))
267         (total (length mark-list)))
268     (wl-folder-confirm-existence (elmo-make-folder wl-spam-folder))
269     (when (memq domain '(undecided good))
270       (message "Registering spam...")
271       (elmo-with-progress-display (> total elmo-display-progress-threshold)
272           (elmo-spam-register total "Registering spam...")
273         (elmo-spam-register-spam-messages (elmo-spam-processor)
274                                           wl-summary-buffer-elmo-folder
275                                           (mapcar #'car mark-list)
276                                           (eq domain 'good)))
277       (message "Registering spam...done"))
278     (wl-summary-move-mark-list-messages mark-list
279                                         wl-spam-folder
280                                         "Refiling spam...")))
281
282 (defun wl-summary-exec-action-refile-with-register (mark-list)
283   (let* ((processor (elmo-spam-processor))
284          (folder wl-summary-buffer-elmo-folder)
285          (domain (wl-spam-domain (elmo-folder-name-internal folder)))
286          spam-list good-list total)
287     (unless (eq domain 'ignore)
288       (dolist (info mark-list)
289         (case (wl-spam-domain (nth 2 info))
290           (spam
291            (setq spam-list (cons (car info) spam-list)))
292           (good
293            (setq good-list (cons (car info) good-list)))))
294       (case domain
295         (spam (setq spam-list nil))
296         (good (setq good-list nil)))
297       (when (or spam-list good-list)
298         (when spam-list
299           (setq total (length spam-list))
300           (message "Registering spam...")
301           (elmo-with-progress-display (> total elmo-display-progress-threshold)
302               (elmo-spam-register total "Registering spam...")
303             (elmo-spam-register-spam-messages processor folder spam-list
304                                               (eq domain 'good)))
305           (message "Registering spam...done"))
306         (when good-list
307           (setq total (length good-list))
308           (message "Registering good...")
309           (elmo-with-progress-display (> total elmo-display-progress-threshold)
310               (elmo-spam-register total "Registering good...")
311             (elmo-spam-register-good-messages processor folder good-list
312                                               (eq domain 'spam)))
313           (message "Registering good...done"))))
314     ;; execute refile messages
315     (wl-summary-exec-action-refile mark-list)))
316
317 (defun wl-message-check-spam ()
318   (let ((original (wl-message-get-original-buffer))
319         (number wl-message-buffer-cur-number)
320         spam)
321     (message "Cheking spam...")
322     (when (setq spam (elmo-spam-buffer-spam-p (elmo-spam-processor) original))
323       (with-current-buffer wl-message-buffer-cur-summary-buffer
324         (wl-summary-spam number)))
325     (message "Cheking spam...done")
326     (message "No: %d is %sa spam message." number (if spam "" "not "))))
327
328 (defun wl-refile-guess-by-spam (entity)
329   (when (elmo-spam-message-spam-p (elmo-spam-processor)
330                                   wl-summary-buffer-elmo-folder
331                                   (elmo-message-entity-number entity))
332     wl-spam-folder))
333
334 (defun wl-spam-setup ()
335   (add-hook 'wl-summary-sync-updated-hook #'wl-summary-auto-check-spam)
336   (let ((actions wl-summary-mark-action-list)
337         action)
338     (while actions
339       (setq action  (car actions)
340             actions (cdr actions))
341       (when (eq (wl-summary-action-symbol action) 'refile)
342         (setcar (nthcdr 4 action) 'wl-summary-exec-action-refile-with-register)
343         (setq actions nil))))
344   (when wl-spam-mark-action-list
345     (setq wl-summary-mark-action-list (append
346                                        wl-summary-mark-action-list
347                                        wl-spam-mark-action-list))
348     (dolist (action wl-spam-mark-action-list)
349       (setq wl-summary-reserve-mark-list
350             (cons (wl-summary-action-mark action)
351                   wl-summary-reserve-mark-list))
352       (setq wl-summary-skip-mark-list
353             (cons (wl-summary-action-mark action)
354                   wl-summary-skip-mark-list))))
355   (define-key wl-summary-mode-map "k" wl-summary-spam-map)
356   (define-key
357     wl-summary-mode-map "mk" 'wl-summary-target-mark-spam)
358   (define-key
359     wl-summary-mode-map "ms" 'wl-summary-target-mark-register-as-spam)
360   (define-key
361     wl-summary-mode-map "mn" 'wl-summary-target-mark-register-as-good))
362
363 (require 'product)
364 (product-provide (provide 'wl-spam) (require 'wl-version))
365
366 (unless noninteractive
367   (wl-spam-setup))
368
369 ;;; wl-spam.el ends here