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)
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.
33 (eval-when-compile (require 'cl))
38 (require 'wl-highlight)
41 "Spam configuration for wanderlust."
44 (defcustom wl-spam-folder "+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")))
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"))
61 (defcustom wl-spam-ignored-folder-list '(wl-draft-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")))
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"))
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"))
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")))
88 (wl-defface wl-highlight-summary-spam-face
93 (:foreground "LightSlateGray")))
94 "Face used for displaying messages mark as spam."
95 :group 'wl-summary-faces
98 (defcustom wl-spam-mark-action-list
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."
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")))
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)
127 ((wl-spam-string-member-p folder-name
128 wl-spam-undecided-folder-list
129 wl-spam-undecided-folder-regexp-list)
131 ((wl-spam-string-member-p folder-name
132 wl-spam-ignored-folder-list
133 wl-spam-ignored-folder-regexp-list)
138 (defun wl-spam-split-numbers (folder numbers)
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)))
146 (setcdr cell (cons number (cdr cell)))
147 (setq alist (cons (list domain number) 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))
158 (dolist (number (elmo-spam-list-spam-messages (elmo-spam-processor)
161 (apply function number args))))
163 (defun wl-spam-apply-partitions (folder partitions function msg)
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 "Register messages specified by FOLDER and NUMBERS as spam.
174 Put spam mark unless FOLDER is a spam folder."
175 (elmo-with-progress-display (elmo-spam-register (length numbers))
177 (elmo-spam-register-spam-messages (elmo-spam-processor)
180 (unless (eq (wl-spam-domain (elmo-folder-name-internal folder))
182 (dolist (number numbers)
183 (wl-summary-spam number))))
185 (defun wl-spam-register-good-messages (folder numbers)
186 "Register messages specified by FOLDER and NUMBERS as non-spam.
188 (elmo-with-progress-display (elmo-spam-register (length numbers))
190 (elmo-spam-register-good-messages (elmo-spam-processor)
193 (dolist (number numbers)
194 (wl-summary-unmark-spam number)))
196 (defun wl-spam-save-status (&optional force)
198 (let ((processor (elmo-spam-processor (not force))))
200 (and processor (elmo-spam-modified-p processor)))
201 (elmo-spam-save-status processor))))
203 ;; insinuate into summary mode
204 (defvar wl-summary-spam-map nil)
206 (unless wl-summary-spam-map
207 (let ((map (make-sparse-keymap)))
208 (define-key map "m" 'wl-summary-spam)
209 (define-key map "c" 'wl-summary-test-spam)
210 (define-key map "C" 'wl-summary-mark-spam)
211 (define-key map "s" 'wl-summary-register-as-spam)
212 (define-key map "S" 'wl-summary-register-as-spam-all)
213 (define-key map "n" 'wl-summary-register-as-good)
214 (define-key map "N" 'wl-summary-register-as-good-all)
215 (setq wl-summary-spam-map map)))
218 ;; Avoid compile warnings
219 (defalias-maybe 'wl-summary-spam 'ignore)
220 (defalias-maybe 'wl-summary-unmark-spam 'ignore))
222 (defun wl-summary-test-spam (&optional folder number)
224 (let ((folder (or folder wl-summary-buffer-elmo-folder))
225 (number (or number (wl-summary-message-number)))
227 (message "Checking spam...")
228 (if (setq spam (elmo-spam-message-spam-p (elmo-spam-processor)
230 (wl-summary-spam number)
231 (wl-summary-unmark-spam number))
232 (message "Checking spam...done")
233 (when (interactive-p)
234 (message "No: %d is %sa spam message." number (if spam "" "not ")))))
236 (defun wl-summary-test-spam-messages (folder numbers &rest args)
237 (elmo-with-progress-display (elmo-spam-check-spam (length numbers))
239 (let* ((spams (elmo-spam-list-spam-messages (elmo-spam-processor)
242 (goods (car (elmo-list-diff numbers spams))))
243 (dolist (number spams)
244 (wl-summary-spam number args))
245 (dolist (number goods)
246 (wl-summary-unmark-spam number)))))
248 (defun wl-summary-test-spam-region (beg end)
250 (let ((numbers (wl-summary-collect-numbers-region beg end)))
252 (wl-summary-test-spam-messages wl-summary-buffer-elmo-folder
255 (message "No message to test.")))))
257 (defun wl-thread-test-spam (&optional arg)
259 (wl-thread-call-region-func 'wl-summary-test-spam-region arg))
261 (defun wl-summary-mark-spam (&optional all)
262 "Set spam mark to messages which is spam classification."
266 (setq numbers wl-summary-buffer-number-list)
267 (dolist (number wl-summary-buffer-number-list)
268 (when (wl-spam-auto-check-message-p wl-summary-buffer-elmo-folder
270 (setq numbers (cons number numbers)))))
272 (wl-spam-map-spam-messages wl-summary-buffer-elmo-folder
276 (message "No message to test.")))))
278 (defun wl-summary-register-as-spam ()
279 "Register current message as spam.
280 Put spam mark unless current folder is a spam folder."
282 (let ((number (wl-summary-message-number)))
284 (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
287 (defun wl-summary-register-as-spam-region (beg end)
288 "Register messages in the region between BEG and END as spam.
289 Put spam mark unless current folder is a spam folder."
291 (let ((numbers (wl-summary-collect-numbers-region beg end)))
293 (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
296 (message "No message to register as spam.")))))
298 (defun wl-thread-register-as-spam (&optional arg)
299 "Register messages which are the descendant of the current thread as spam.
300 Put spam mark unless current folder is a spam folder.
301 With prefix argument, it affects on the all messages in the thread tree."
303 (wl-thread-call-region-func 'wl-summary-register-as-spam-region arg))
305 (defun wl-summary-register-as-spam-all ()
306 "Register all messages in the folder as spam.
307 Put spam mark unless current folder is a spam folder."
309 (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
310 wl-summary-buffer-number-list))
312 (defun wl-summary-target-mark-register-as-spam ()
313 "Register messages with the target mark as spam.
314 Put spam mark unless current folder is a spam folder."
317 (goto-char (point-min))
318 (let ((inhibit-read-only t)
319 (buffer-read-only nil)
320 wl-summary-buffer-disp-msg)
321 (wl-spam-register-spam-messages wl-summary-buffer-elmo-folder
322 wl-summary-buffer-target-mark-list)
323 (dolist (number wl-summary-buffer-target-mark-list)
324 (wl-summary-unset-mark number)))))
326 (defun wl-summary-register-as-good ()
327 "Register current message as non-spam.
330 (let ((number (wl-summary-message-number)))
332 (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
335 (defun wl-summary-register-as-good-region (beg end)
336 "Register messages in the region between BEG and END as non-spam.
339 (let ((numbers (wl-summary-collect-numbers-region beg end)))
341 (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
344 (message "No message to register as good.")))))
346 (defun wl-thread-register-as-good (&optional arg)
347 "Register messages which are the descendant of the current thread as non-spam.
349 With prefix argument, it affects on the all messages in the thread tree."
351 (wl-thread-call-region-func 'wl-summary-register-as-good-region arg))
353 (defun wl-summary-register-as-good-all ()
354 "Register all messages in the folder as non-spam.
357 (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
358 wl-summary-buffer-number-list))
360 (defun wl-summary-target-mark-register-as-good ()
361 "Register messages with the target mark as non-spam.
365 (goto-char (point-min))
366 (let ((inhibit-read-only t)
367 (buffer-read-only nil)
368 wl-summary-buffer-disp-msg)
369 (wl-spam-register-good-messages wl-summary-buffer-elmo-folder
370 wl-summary-buffer-target-mark-list)
371 (dolist (number wl-summary-buffer-target-mark-list)
372 (wl-summary-unset-mark number)))))
374 ;; hook functions and other
375 (defun wl-summary-auto-check-spam ()
376 (when (elmo-string-match-member (wl-summary-buffer-folder-name)
377 wl-spam-auto-check-folder-regexp-list)
378 (wl-summary-mark-spam)))
380 (defun wl-summary-exec-action-spam (mark-list)
381 (let ((folder wl-summary-buffer-elmo-folder))
382 (wl-folder-confirm-existence (wl-folder-get-elmo-folder wl-spam-folder))
383 (wl-spam-apply-partitions
385 (wl-filter-associations
387 (wl-spam-split-numbers folder (mapcar #'car mark-list)))
388 (lambda (folder numbers domain)
389 (elmo-spam-register-spam-messages (elmo-spam-processor)
393 (wl-summary-move-mark-list-messages mark-list
397 (defun wl-summary-exec-action-refile-with-register (mark-list)
398 (let ((folder wl-summary-buffer-elmo-folder)
400 (dolist (info mark-list)
401 (case (wl-spam-domain (nth 2 info))
403 (setq spam-list (cons (car info) spam-list)))
405 (setq good-list (cons (car info) good-list)))))
406 (wl-spam-apply-partitions
408 (wl-filter-associations '(undecided good)
409 (wl-spam-split-numbers folder spam-list))
410 (lambda (folder numbers domain)
411 (elmo-spam-register-spam-messages (elmo-spam-processor)
415 (wl-spam-apply-partitions
417 (wl-filter-associations '(undecided spam)
418 (wl-spam-split-numbers folder good-list))
419 (lambda (folder numbers domain)
420 (elmo-spam-register-good-messages (elmo-spam-processor)
424 ;; execute refile messages
425 (wl-summary-exec-action-refile mark-list)))
427 (defun wl-message-check-spam ()
428 (let ((original (wl-message-get-original-buffer))
429 (number wl-message-buffer-cur-number)
431 (message "Checking spam...")
432 (when (setq spam (elmo-spam-buffer-spam-p (elmo-spam-processor) original))
433 (with-current-buffer wl-message-buffer-cur-summary-buffer
434 (wl-summary-spam number)))
435 (message "Checking spam...done")
436 (message "No: %d is %sa spam message." number (if spam "" "not "))))
438 (defun wl-refile-guess-by-spam (entity)
439 (when (elmo-spam-message-spam-p (elmo-spam-processor)
440 wl-summary-buffer-elmo-folder
441 (elmo-message-entity-number entity))
444 (defun wl-spam-setup ()
445 (add-hook 'wl-summary-sync-updated-hook #'wl-summary-auto-check-spam)
446 (let ((actions wl-summary-mark-action-list)
449 (setq action (car actions)
450 actions (cdr actions))
451 (when (eq (wl-summary-action-symbol action) 'refile)
452 (setcar (nthcdr 4 action) 'wl-summary-exec-action-refile-with-register)
453 (setq actions nil))))
454 (when wl-spam-mark-action-list
455 (setq wl-summary-mark-action-list (append
456 wl-summary-mark-action-list
457 wl-spam-mark-action-list))
458 (dolist (action wl-spam-mark-action-list)
459 (setq wl-summary-reserve-mark-list
460 (cons (wl-summary-action-mark action)
461 wl-summary-reserve-mark-list))
462 (setq wl-summary-skip-mark-list
463 (cons (wl-summary-action-mark action)
464 wl-summary-skip-mark-list))))
465 (define-key wl-summary-mode-map "k" wl-summary-spam-map)
467 wl-summary-mode-map "rkm" 'wl-summary-spam-region)
469 wl-summary-mode-map "rkc" 'wl-summary-test-spam-region)
471 wl-summary-mode-map "rks" 'wl-summary-register-as-spam-region)
473 wl-summary-mode-map "rkn" 'wl-summary-register-as-good-region)
475 wl-summary-mode-map "tkm" 'wl-thread-spam)
477 wl-summary-mode-map "tkc" 'wl-thread-test-spam)
479 wl-summary-mode-map "tks" 'wl-thread-register-as-spam)
481 wl-summary-mode-map "tkn" 'wl-thread-register-as-good)
483 wl-summary-mode-map "mk" 'wl-summary-target-mark-spam)
485 wl-summary-mode-map "ms" 'wl-summary-target-mark-register-as-spam)
487 wl-summary-mode-map "mn" 'wl-summary-target-mark-register-as-good))
490 (product-provide (provide 'wl-spam) (require 'wl-version))
492 (unless noninteractive
495 ;;; wl-spam.el ends here