Merge branch 'master' of https://git.gnus.org/gnus
[gnus] / lisp / spam-stat.el
1 ;;; spam-stat.el --- detecting spam based on statistics
2
3 ;; Copyright (C) 2002-2011  Free Software Foundation, Inc.
4
5 ;; Author: Alex Schroeder <alex@gnu.org>
6 ;; Keywords: network
7 ;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?SpamStat
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs 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 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; GNU Emacs 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.  If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;; This implements spam analysis according to Paul Graham in "A Plan
27 ;; for Spam".  The basis for all this is a statistical distribution of
28 ;; words for your spam and non-spam mails.  We need this information
29 ;; in a hash-table so that the analysis can use the information when
30 ;; looking at your mails.  Therefore, before you begin, you need tons
31 ;; of mails (Graham uses 4000 non-spam and 4000 spam mails for his
32 ;; experiments).
33 ;;
34 ;; The main interface to using spam-stat, are the following functions:
35 ;;
36 ;; `spam-stat-buffer-is-spam' -- called in a buffer, that buffer is
37 ;; considered to be a new spam mail; use this for new mail that has
38 ;; not been processed before
39 ;;
40 ;; `spam-stat-buffer-is-non-spam' -- called in a buffer, that buffer
41 ;; is considered to be a new non-spam mail; use this for new mail that
42 ;; has not been processed before
43 ;;
44 ;; `spam-stat-buffer-change-to-spam' -- called in a buffer, that
45 ;; buffer is no longer considered to be normal mail but spam; use this
46 ;; to change the status of a mail that has already been processed as
47 ;; non-spam
48 ;;
49 ;; `spam-stat-buffer-change-to-non-spam' -- called in a buffer, that
50 ;; buffer is no longer considered to be spam but normal mail; use this
51 ;; to change the status of a mail that has already been processed as
52 ;; spam
53 ;;
54 ;; `spam-stat-save' -- save the hash table to the file; the filename
55 ;; used is stored in the variable `spam-stat-file'
56 ;;
57 ;; `spam-stat-load' -- load the hash table from a file; the filename
58 ;; used is stored in the variable `spam-stat-file'
59 ;;
60 ;; `spam-stat-score-word' -- return the spam score for a word
61 ;;
62 ;; `spam-stat-score-buffer' -- return the spam score for a buffer
63 ;;
64 ;; `spam-stat-split-fancy' -- for fancy mail splitting; add
65 ;; the rule (: spam-stat-split-fancy) to `nnmail-split-fancy'
66 ;;
67 ;; This requires the following in your ~/.gnus file:
68 ;;
69 ;; (require 'spam-stat)
70 ;; (spam-stat-load)
71
72 ;;; Testing:
73
74 ;; Typical test will involve calls to the following functions:
75 ;;
76 ;; Reset: (spam-stat-reset)
77 ;; Learn spam: (spam-stat-process-spam-directory "~/Mail/mail/spam")
78 ;; Learn non-spam: (spam-stat-process-non-spam-directory "~/Mail/mail/misc")
79 ;; Save table: (spam-stat-save)
80 ;; File size: (nth 7 (file-attributes spam-stat-file))
81 ;; Number of words: (hash-table-count spam-stat)
82 ;; Test spam: (spam-stat-test-directory "~/Mail/mail/spam")
83 ;; Test non-spam: (spam-stat-test-directory "~/Mail/mail/misc")
84 ;; Reduce table size: (spam-stat-reduce-size)
85 ;; Save table: (spam-stat-save)
86 ;; File size: (nth 7 (file-attributes spam-stat-file))
87 ;; Number of words: (hash-table-count spam-stat)
88 ;; Test spam: (spam-stat-test-directory "~/Mail/mail/spam")
89 ;; Test non-spam: (spam-stat-test-directory "~/Mail/mail/misc")
90
91 ;;; Dictionary Creation:
92
93 ;; Typically, you will filter away mailing lists etc. using specific
94 ;; rules in `nnmail-split-fancy'.  Somewhere among these rules, you
95 ;; will filter spam.  Here is how you would create your dictionary:
96
97 ;; Reset: (spam-stat-reset)
98 ;; Learn spam: (spam-stat-process-spam-directory "~/Mail/mail/spam")
99 ;; Learn non-spam: (spam-stat-process-non-spam-directory "~/Mail/mail/misc")
100 ;; Repeat for any other non-spam group you need...
101 ;; Reduce table size: (spam-stat-reduce-size)
102 ;; Save table: (spam-stat-save)
103
104 ;;; Todo:
105
106 ;; Speed it up.  Integrate with Gnus such that it uses spam and expiry
107 ;; marks to call the appropriate functions when leaving the summary
108 ;; buffer and saves the hash table when leaving Gnus.  More testing:
109 ;; More mails, disabling SpamAssassin, double checking algorithm, find
110 ;; improved algorithm.
111
112 ;;; Thanks:
113
114 ;; Ted Zlatanov <tzz@lifelogs.com>
115 ;; Jesper Harder <harder@myrealbox.com>
116 ;; Dan Schmidt <dfan@dfan.org>
117 ;; Lasse Rasinen <lrasinen@iki.fi>
118 ;; Milan Zamazal <pdm@zamazal.org>
119
120 \f
121
122 ;;; Code:
123 (require 'mail-parse)
124
125 (defvar gnus-original-article-buffer)
126
127 (defgroup spam-stat nil
128   "Statistical spam detection for Emacs.
129 Use the functions to build a dictionary of words and their statistical
130 distribution in spam and non-spam mails.  Then use a function to determine
131 whether a buffer contains spam or not."
132   :version "22.1"
133   :group 'gnus)
134
135 (defcustom spam-stat-file "~/.spam-stat.el"
136   "File used to save and load the dictionary.
137 See `spam-stat-to-hash-table' for the format of the file."
138   :type 'file
139   :group 'spam-stat)
140
141 (defcustom spam-stat-install-hooks t
142   "Whether spam-stat should install its hooks in Gnus.
143 This is set to nil if you use spam-stat through spam.el."
144   :type 'boolean
145   :group 'spam-stat)
146
147 (defcustom spam-stat-unknown-word-score 0.2
148   "The score to use for unknown words.
149 Also used for words that don't appear often enough."
150   :type 'number
151   :group 'spam-stat)
152
153 (defcustom spam-stat-max-word-length 15
154   "Only words shorter than this will be considered."
155   :type 'integer
156   :group 'spam-stat)
157
158 (defcustom spam-stat-max-buffer-length 10240
159   "Only the beginning of buffers will be analyzed.
160 This variable says how many characters this will be."
161   :type 'integer
162   :group 'spam-stat)
163
164 (defcustom spam-stat-split-fancy-spam-group "mail.spam"
165   "Name of the group where spam should be stored.
166 If `spam-stat-split-fancy' is used in fancy splitting rules.  Has
167 no effect when spam-stat is invoked through spam.el."
168   :type 'string
169   :group 'spam-stat)
170
171 (defcustom spam-stat-split-fancy-spam-threshold 0.9
172   "Spam score threshold in spam-stat-split-fancy."
173   :type 'number
174   :group 'spam-stat)
175
176 (defcustom spam-stat-washing-hook nil
177   "Hook applied to each message before analysis."
178   :type 'hook
179   :group 'spam-stat)
180
181 (defcustom spam-stat-score-buffer-user-functions nil
182   "List of additional scoring functions.
183 Called  one by one on the buffer.
184
185 If all of these functions return non-nil answers, these numerical
186 answers are added to the computed spam stat score on the buffer.  If
187 you defun such functions, make sure they don't return the buffer in a
188 narrowed state or such: use, for example, `save-excursion'.  Each of
189 your functions is also passed the initial spam-stat score which might
190 aid in your scoring.
191
192 Also be careful when defining such functions.  If they take a long
193 time, they will slow down your mail splitting.  Thus, if the buffer is
194 large, don't forget to use smaller regions, by wrapping your work in,
195 say, `with-spam-stat-max-buffer-size'."
196   :type '(repeat sexp)
197   :group 'spam-stat)
198
199 (defcustom spam-stat-process-directory-age 90
200   "Max. age of files to be processed in directory, in days.
201 When using `spam-stat-process-spam-directory' or
202 `spam-stat-process-non-spam-directory', only files that have
203 been touched in this many days will be considered.  Without
204 this filter, re-training spam-stat with several thousand messages
205 will start to take a very long time."
206   :type 'number
207   :group 'spam-stat)
208
209 (defvar spam-stat-last-saved-at nil
210   "Time stamp of last change of spam-stat-file on this run")
211
212 (defvar spam-stat-syntax-table
213   (let ((table (copy-syntax-table text-mode-syntax-table)))
214     (modify-syntax-entry ?- "w" table)
215     (modify-syntax-entry ?_ "w" table)
216     (modify-syntax-entry ?. "w" table)
217     (modify-syntax-entry ?! "w" table)