1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
;;; gnus-logic.el --- advanced scoring code for Gnus -*- lexical-binding: t; -*-
;; Copyright (C) 1996-2025 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Keywords: news
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(eval-when-compile (require 'cl-lib))
(require 'gnus)
(require 'gnus-score)
(require 'gnus-util)
;;; Internal variables.
(defvar gnus-advanced-headers nil)
;; To avoid having 8-bit characters in the source file.
(defvar gnus-advanced-not (intern (format "%c" 172)))
(defconst gnus-advanced-index
;; Name to index alist.
'(("number" 0 gnus-advanced-integer)
("subject" 1 gnus-advanced-string)
("from" 2 gnus-advanced-string)
("date" 3 gnus-advanced-date)
("message-id" 4 gnus-advanced-string)
("references" 5 gnus-advanced-string)
("chars" 6 gnus-advanced-integer)
("lines" 7 gnus-advanced-integer)
("xref" 8 gnus-advanced-string)
("head" nil gnus-advanced-body)
("body" nil gnus-advanced-body)
("all" nil gnus-advanced-body)))
(autoload 'parse-time-string "parse-time")
(defun gnus-score-advanced (rule &optional trace)
"Apply advanced scoring RULE to all the articles in the current group."
(let (new-score score multiple)
(dolist (gnus-advanced-headers gnus-newsgroup-headers)
(when (setq multiple (gnus-advanced-score-rule (car rule)))
(setq new-score (or (nth 1 rule)
gnus-score-interactive-default-score))
(when (numberp multiple)
(setq new-score (* multiple new-score)))
;; This rule was successful, so we add the score to this
;; article.
(if (setq score (assq (mail-header-number gnus-advanced-headers)
gnus-newsgroup-scored))
(setcdr score
(+ (cdr score) new-score))
(push (cons (mail-header-number gnus-advanced-headers)
new-score)
gnus-newsgroup-scored))
(when trace
(push (cons "A file" rule)
;; Must be synced with `gnus-score-edit-file-at-point'.
gnus-score-trace))))))
(defun gnus-advanced-score-rule (rule)
"Apply RULE to `gnus-advanced-headers'."
(let ((type (car rule)))
(cond
;; "And" rule.
((or (eq type '&) (eq type 'and))
(pop rule)
(if (not rule)
t ; Empty rule is true.
(while (and rule
(gnus-advanced-score-rule (car rule)))
(pop rule))
;; If all the rules were true, then `rule' should be nil.
(not rule)))
;; "Or" rule.
((or (eq type '|) (eq type 'or))
(pop rule)
(if (not rule)
nil
(while (and rule
(not (gnus-advanced-score-rule (car rule))))
(pop rule))
;; If one of the rules returned true, then `rule' should be non-nil.
rule))
;; "Not" rule.
((or (eq type '!) (eq type 'not) (eq type gnus-advanced-not))
(not (gnus-advanced-score-rule (nth 1 rule))))
;; This is a `1-'-type redirection rule.
((and (symbolp type)
(string-match "^[0-9]+-$\\|^\\^+$" (symbol-name type)))
(let ((gnus-advanced-headers
(gnus-parent-headers
gnus-advanced-headers
(if (string-match "^\\([0-9]+\\)-$" (symbol-name type))
;; 1- type redirection.
(string-to-number
(substring (symbol-name type)
(match-beginning 1) (match-end 1)))
;; ^^^ type redirection.
(length (symbol-name type))))))
(when gnus-advanced-headers
(gnus-advanced-score-rule (nth 1 rule)))))
;; Plain scoring rule.
((stringp type)
(gnus-advanced-score-article rule))
;; Bug-out time!
(t
(error "Unknown advanced score type: %s" rule)))))
(defun gnus-advanced-score-article (rule)
;; `rule' is a semi-normal score rule, so we find out what function
;; that's supposed to do the actual processing.
(let* ((header (car rule))
(func (assoc (downcase header) gnus-advanced-index)))
(if (not func)
(error "No such header: %s" rule)
;; Call the score function.
(funcall (caddr func) (or (cadr func) header)
(cadr rule) (caddr rule)))))
(defun gnus-advanced-string (index match type)
"See whether string MATCH of TYPE matches `gnus-advanced-headers' in INDEX."
(let* ((type (or type 's))
(case-fold-search (not (eq (downcase (symbol-name type))
(symbol-name type))))
(header (or (aref gnus-advanced-headers index) "")))
(cond
((memq type '(r R regexp Regexp))
(string-match match header))
((memq type '(s S string String))
(string-match (regexp-quote match) header))
((memq type '(e E exact Exact))
(string= match header))
((memq type '(f F fuzzy Fuzzy))
(string-match (regexp-quote (gnus-simplify-subject-fuzzy match))
header))
(t
(error "No such string match type: %s" type)))))
(defun gnus-advanced-integer (index match type)
(if (not (memq type '(< > <= >= =)))
(error "No such integer score type: %s" type)
(funcall type (or (aref gnus-advanced-headers index) 0) match)))
(defun gnus-advanced-date (index match type)
(let ((date (encode-time (parse-time-string
(aref gnus-advanced-headers index))))
(match (encode-time (parse-time-string match))))
(cond
((eq type 'at)
(equal date match))
((eq type 'before)
(time-less-p match date))
((eq type 'after)
(time-less-p date match))
(t
(error "No such date score type: %s" type)))))
(defun gnus-advanced-body (header match type)
(when (string= header "all")
(setq header "article"))
(with-current-buffer nntp-server-buffer
(let* ((request-func (cond ((string= "head" header)
'gnus-request-head)
((string= "body" header)
'gnus-request-body)
(t 'gnus-request-article)))
ofunc article handles)
;; Not all backends support partial fetching. In that case, we
;; just fetch the entire article.
;; When scoring by body, we need to peek at the headers to detect the
;; content encoding
(unless (or (gnus-check-backend-function
(intern (concat "request-" header))
gnus-newsgroup-name)
(string= "body" header))
(setq ofunc request-func)
(setq request-func 'gnus-request-article))
(setq article (mail-header-number gnus-advanced-headers))
(gnus-message 7 "Scoring article %s..." article)
(when (funcall request-func article gnus-newsgroup-name)
(when (string= "body" header)
(setq handles (gnus-score-decode-text-parts)))
(goto-char (point-min))
;; If just parts of the article is to be searched and the
;; backend didn't support partial fetching, we just narrow to
;; the relevant parts.
(when ofunc
(if (eq ofunc 'gnus-request-head)
(narrow-to-region
(point)
(or (search-forward "\n\n" nil t) (point-max)))
(narrow-to-region
(or (search-forward "\n\n" nil t) (point))
(point-max))))
(let* ((case-fold-search (not (eq (downcase (symbol-name type))
(symbol-name type))))
(search-func
(cond ((memq type '(r R regexp Regexp))
're-search-forward)
((memq type '(s S string String))
'search-forward)
(t
(error "Invalid match type: %s" type)))))
(goto-char (point-min))
(prog1
(funcall search-func match nil t)
(widen)
(when handles (mm-destroy-parts handles))))))))
(provide 'gnus-logic)
;;; gnus-logic.el ends here
|