1 /**
2  * @file
3  * Routines for adding user scores to emails
4  *
5  * @authors
6  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page neo_score Routines for adding user scores to emails
25  *
26  * Routines for adding user scores to emails
27  */
28 
29 #include "config.h"
30 #include <stdbool.h>
31 #include <stdlib.h>
32 #include "mutt/lib.h"
33 #include "config/lib.h"
34 #include "email/lib.h"
35 #include "core/lib.h"
36 #include "mutt.h"
37 #include "score.h"
38 #include "pattern/lib.h"
39 #include "context.h"
40 #include "init.h"
41 #include "mutt_globals.h"
42 #include "mutt_thread.h"
43 #include "options.h"
44 #include "protos.h"
45 
46 /**
47  * struct Score - Scoring rule for email
48  */
49 struct Score
50 {
51   char *str;
52   struct PatternList *pat;
53   int val;
54   bool exact; ///< if this rule matches, don't evaluate any more
55   struct Score *next;
56 };
57 
58 static struct Score *ScoreList = NULL;
59 
60 /**
61  * mutt_check_rescore - Do the emails need to have their scores recalculated?
62  * @param m Mailbox
63  */
mutt_check_rescore(struct Mailbox * m)64 void mutt_check_rescore(struct Mailbox *m)
65 {
66   const bool c_score = cs_subset_bool(NeoMutt->sub, "score");
67   if (OptNeedRescore && c_score)
68   {
69     const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
70     const short c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
71     if (((c_sort & SORT_MASK) == SORT_SCORE) || ((c_sort_aux & SORT_MASK) == SORT_SCORE))
72     {
73       OptNeedResort = true;
74       if (mutt_using_threads())
75         OptSortSubthreads = true;
76     }
77 
78     mutt_debug(LL_NOTIFY, "NT_SCORE: %p\n", m);
79     notify_send(m->notify, NT_SCORE, 0, NULL);
80   }
81   OptNeedRescore = false;
82 }
83 
84 /**
85  * mutt_parse_score - Parse the 'score' command - Implements Command::parse() - @ingroup command_parse
86  */
mutt_parse_score(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)87 enum CommandResult mutt_parse_score(struct Buffer *buf, struct Buffer *s,
88                                     intptr_t data, struct Buffer *err)
89 {
90   struct Score *ptr = NULL, *last = NULL;
91   char *pattern = NULL, *pc = NULL;
92 
93   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
94   if (!MoreArgs(s))
95   {
96     mutt_buffer_printf(err, _("%s: too few arguments"), "score");
97     return MUTT_CMD_WARNING;
98   }
99   pattern = mutt_buffer_strdup(buf);
100   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
101   if (MoreArgs(s))
102   {
103     FREE(&pattern);
104     mutt_buffer_printf(err, _("%s: too many arguments"), "score");
105     return MUTT_CMD_WARNING;
106   }
107 
108   /* look for an existing entry and update the value, else add it to the end
109    * of the list */
110   for (ptr = ScoreList, last = NULL; ptr; last = ptr, ptr = ptr->next)
111     if (mutt_str_equal(pattern, ptr->str))
112       break;
113   if (!ptr)
114   {
115     struct PatternList *pat =
116         mutt_pattern_comp(ctx_mailbox(Context), Context ? Context->menu : NULL,
117                           pattern, MUTT_PC_NO_FLAGS, err);
118     if (!pat)
119     {
120       FREE(&pattern);
121       return MUTT_CMD_ERROR;
122     }
123     ptr = mutt_mem_calloc(1, sizeof(struct Score));
124     if (last)
125       last->next = ptr;
126     else
127       ScoreList = ptr;
128     ptr->pat = pat;
129     ptr->str = pattern;
130   }
131   else
132   {
133     /* 'buf' arg was cleared and 'pattern' holds the only reference;
134      * as here 'ptr' != NULL -> update the value only in which case
135      * ptr->str already has the string, so pattern should be freed.  */
136     FREE(&pattern);
137   }
138   pc = buf->data;
139   if (*pc == '=')
140   {
141     ptr->exact = true;
142     pc++;
143   }
144   if (mutt_str_atoi(pc, &ptr->val) < 0)
145   {
146     FREE(&pattern);
147     mutt_buffer_strcpy(err, _("Error: score: invalid number"));
148     return MUTT_CMD_ERROR;
149   }
150   OptNeedRescore = true;
151   return MUTT_CMD_SUCCESS;
152 }
153 
154 /**
155  * mutt_score_message - Apply scoring to an email
156  * @param m        Mailbox
157  * @param e        Email
158  * @param upd_mbox If true, update the Mailbox too
159  */
mutt_score_message(struct Mailbox * m,struct Email * e,bool upd_mbox)160 void mutt_score_message(struct Mailbox *m, struct Email *e, bool upd_mbox)
161 {
162   struct Score *tmp = NULL;
163   struct PatternCache cache = { 0 };
164 
165   e->score = 0; /* in case of re-scoring */
166   for (tmp = ScoreList; tmp; tmp = tmp->next)
167   {
168     if (mutt_pattern_exec(SLIST_FIRST(tmp->pat), MUTT_MATCH_FULL_ADDRESS, NULL, e, &cache) > 0)
169     {
170       if (tmp->exact || (tmp->val == 9999) || (tmp->val == -9999))
171       {
172         e->score = tmp->val;
173         break;
174       }
175       e->score += tmp->val;
176     }
177   }
178   if (e->score < 0)
179     e->score = 0;
180 
181   const short c_score_threshold_delete =
182       cs_subset_number(NeoMutt->sub, "score_threshold_delete");
183   const short c_score_threshold_flag =
184       cs_subset_number(NeoMutt->sub, "score_threshold_flag");
185   const short c_score_threshold_read =
186       cs_subset_number(NeoMutt->sub, "score_threshold_read");
187 
188   if (e->score <= c_score_threshold_delete)
189     mutt_set_flag_update(m, e, MUTT_DELETE, true, upd_mbox);
190   if (e->score <= c_score_threshold_read)
191     mutt_set_flag_update(m, e, MUTT_READ, true, upd_mbox);
192   if (e->score >= c_score_threshold_flag)
193     mutt_set_flag_update(m, e, MUTT_FLAG, true, upd_mbox);
194 }
195 
196 /**
197  * mutt_parse_unscore - Parse the 'unscore' command - Implements Command::parse() - @ingroup command_parse
198  */
mutt_parse_unscore(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)199 enum CommandResult mutt_parse_unscore(struct Buffer *buf, struct Buffer *s,
200                                       intptr_t data, struct Buffer *err)
201 {
202   struct Score *tmp = NULL, *last = NULL;
203 
204   while (MoreArgs(s))
205   {
206     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
207     if (mutt_str_equal("*", buf->data))
208     {
209       for (tmp = ScoreList; tmp;)
210       {
211         last = tmp;
212         tmp = tmp->next;
213         mutt_pattern_free(&last->pat);
214         FREE(&last);
215       }
216       ScoreList = NULL;
217     }
218     else
219     {
220       for (tmp = ScoreList; tmp; last = tmp, tmp = tmp->next)
221       {
222         if (mutt_str_equal(buf->data, tmp->str))
223         {
224           if (last)
225             last->next = tmp->next;
226           else
227             ScoreList = tmp->next;
228           mutt_pattern_free(&tmp->pat);
229           FREE(&tmp);
230           /* there should only be one score per pattern, so we can stop here */
231           break;
232         }
233       }
234     }
235   }
236   OptNeedRescore = true;
237   return MUTT_CMD_SUCCESS;
238 }
239