color cosmetix
[chiroptera.git] / hitwit.d
blobd1c0c0f5e9645f9bc030a9554b7b17ff58c77d3e
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module hitwit is aliced;
19 private:
21 import iv.cmdcon;
22 import iv.strex;
23 import iv.vfs.io;
25 import maildb;
27 import egui;
29 import folder;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public abstract class Highlighter {
34 string foldermask;
36 bool isOurFolder (Folder fld) nothrow {
37 import std.path : globMatch;
38 if (fld is null) return false;
39 return globMatch(fld.folderPath, foldermask);
42 bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow; // no need to check folder here, it is passed just for informational purposes
43 string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow; // no need to check folder here, it is passed just for informational purposes
47 final class HighlightFrom : Highlighter {
48 string email;
49 bool anchorStart, anchorEnd;
51 override bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow {
52 if (art is null) return false;
53 string pat = email;
54 string cs = art.frommail;
55 if (pat.length == 0) return false;
56 if (anchorStart) {
57 if (anchorEnd && cs.length != pat.length) return false;
58 if (cs.startsWithCI(pat)) return true;
59 } else if (anchorEnd) {
60 assert(!anchorStart);
61 if (cs.length < pat.length) return false;
62 if (cs[$-pat.length..$].strEquCI(pat)) return true;
63 } else {
64 assert(!anchorStart);
65 assert(!anchorEnd);
66 while (cs.length >= pat.length) {
67 if (cs.startsWithCI(pat)) return true;
68 cs = cs[1..$];
71 return false;
74 override string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow { return null; }
78 final class TwitThread : Highlighter {
79 string msgid;
81 override bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow { return false; }
83 override string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow {
84 if (msgid.length == 0) return null;
85 while (art !is null) {
86 if (art.msgid == msgid) return "";
87 aidx = art.parent;
88 if (aidx == 0) break;
89 art = abase[aidx];
91 return null;
96 final class TwitNick : Highlighter {
97 string name;
98 string mail;
99 string title;
100 string msgid;
101 string url;
103 private final string checkArt (Article art) nothrow @nogc {
104 if (art is null) return null;
105 if (name.length == 0 && mail.length == 0) return null;
106 if (name.length && art.fromname != name) return null;
107 if (mail.length && !art.frommail.strEquCI(mail)) return null;
108 return (title !is null ? title : "");
111 override bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow { return false; }
113 override string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow {
114 auto res = checkArt(art);
115 while (res is null && art !is null) {
116 aidx = art.parent;
117 if (aidx == 0) break;
118 art = abase[aidx];
119 res = checkArt(art);
121 return res;
125 private __gshared Highlighter[] hitwlist;
126 public __gshared uint hitwlistUpdateCounter;
129 // ////////////////////////////////////////////////////////////////////////// //
130 public bool forEachHitTwit (scope bool delegate (Highlighter ht) nothrow dg) nothrow {
131 if (dg is null) return false;
132 foreach (Highlighter hg; hitwlist) {
133 if (dg(hg)) return true;
135 return false;
139 // ////////////////////////////////////////////////////////////////////////// //
140 // cannot use module ctor due to cyclic dependencies
141 public void hitwitInitConsole () {
142 //highlight fromemail ^ketmar@ketmar.no-ip.org$ folder_mask dmars_ng/*
143 conRegFunc!((ConString[] args) {
144 auto origargs = args;
145 auto hf = new HighlightFrom();
146 while (args.length) {
147 if (args.length < 2) { conwriteln("highlight: invalid args: ", origargs); return; }
148 auto opt = args[0];
149 auto arg = args[1];
150 args = args[2..$];
151 switch (opt) {
152 case "fromemail":
153 case "from_email":
154 if (hf.email.length != 0) {
155 conwriteln("highlight: duplicate email option: '", arg, "'");
156 conwriteln("highlight: invalid args: ", origargs);
157 return;
159 hf.email = arg.idup;
160 break;
161 case "foldermask":
162 case "folder_mask":
163 if (hf.foldermask.length != 0) {
164 conwriteln("highlight: duplicate folder mask option: '", arg, "'");
165 conwriteln("highlight: invalid args: ", origargs);
166 return;
168 hf.foldermask = arg.idup;
169 break;
170 default:
171 conwriteln("highlight: unknown options: '", opt, "'");
172 conwriteln("highlight: invalid args: ", origargs);
173 return;
176 if (hf.email.length && hf.email[0] == '^') { hf.email = hf.email[1..$]; hf.anchorStart = true; }
177 if (hf.email.length && hf.email[$-1] == '$') { hf.email = hf.email[0..$-1]; hf.anchorEnd = true; }
178 if (hf.email.length == 0) { conwriteln("highlight: invalid args (no email): ", origargs); return; }
179 if (hf.foldermask.length == 0) { conwriteln("highlight: invalid args (no folder mask): ", origargs); return; }
180 hitwlist ~= hf;
181 // HACK! this is to avoid false positives in GC
182 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
183 //conwriteln("new highlihgter: ", origargs);
184 ++hitwlistUpdateCounter;
185 postScreenRebuild();
186 })("highlight", "highight messages in thread pane");
188 //twit_thread folder_mask dmars_ng/* msgid <nsp82c$t2g$1@digitalmars.com>
189 conRegFunc!((ConString[] args) {
190 auto origargs = args;
191 auto hf = new TwitThread();
192 while (args.length) {
193 if (args.length < 2) { conwriteln("twit: invalid args: ", origargs); return; }
194 auto opt = args[0];
195 auto arg = args[1];
196 args = args[2..$];
197 switch (opt) {
198 case "foldermask":
199 case "folder_mask":
200 if (hf.foldermask.length != 0) {
201 conwriteln("twit: duplicate folder mask option: '", arg, "'");
202 conwriteln("twit: invalid args: ", origargs);
203 return;
205 hf.foldermask = arg.idup;
206 break;
207 case "msgid":
208 case "message":
209 case "messageid":
210 case "message_id":
211 if (hf.msgid.length != 0) {
212 conwriteln("twit: duplicate message id option: '", arg, "'");
213 conwriteln("twit: invalid args: ", origargs);
214 return;
216 hf.msgid = arg.idup;
217 break;
218 default:
219 conwriteln("twit: unknown options: '", opt, "'");
220 conwriteln("twit: invalid args: ", origargs);
221 return;
224 if (hf.msgid.length == 0) { conwriteln("twit: invalid args (no msgid): ", origargs); return; }
225 if (hf.foldermask.length == 0) { conwriteln("twit: invalid args (no folder mask): ", origargs); return; }
226 hitwlist ~= hf;
227 // HACK! this is to avoid false positives in GC
228 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
229 //conwriteln("new highlihgter: ", origargs);
230 ++hitwlistUpdateCounter;
231 postScreenRebuild();
232 })("twit_thread", "twit threads");
234 //twit_set
235 // folder_mask glob
236 // title "title"
237 // name "name"
238 // mail "mail"
239 // message "msgid"
240 // url "url"
241 conRegFunc!((ConString[] args) {
242 auto origargs = args;
243 auto hf = new TwitNick();
244 while (args.length) {
245 if (args.length < 2) { conwriteln("twit_set: invalid args: ", origargs); return; }
246 auto opt = args[0];
247 auto arg = args[1];
248 args = args[2..$];
249 switch (opt) {
250 case "foldermask":
251 case "folder_mask":
252 if (hf.foldermask.length != 0) {
253 conwriteln("twit_set: duplicate folder mask option: '", arg, "'");
254 conwriteln("twit_set: invalid args: ", origargs);
255 return;
257 hf.foldermask = arg.idup;
258 break;
259 case "msgid":
260 case "message":
261 case "messageid":
262 case "message_id":
263 if (hf.msgid.length != 0) {
264 conwriteln("twit_set: duplicate message id option: '", arg, "'");
265 conwriteln("twit_set: invalid args: ", origargs);
266 return;
268 hf.msgid = arg.idup;
269 break;
270 case "title":
271 if (hf.title !is null) {
272 conwriteln("twit_set: duplicate title option: '", arg, "'");
273 conwriteln("twit_set: invalid args: ", origargs);
274 return;
276 hf.title = (arg.length ? arg.idup : "");
277 break;
278 case "name":
279 if (hf.name.length != 0) {
280 conwriteln("twit_set: duplicate name option: '", arg, "'");
281 conwriteln("twit_set: invalid args: ", origargs);
282 return;
284 hf.name = arg.idup;
285 break;
286 case "mail":
287 case "email":
288 case "e-mail":
289 if (hf.mail.length != 0) {
290 conwriteln("twit_set: duplicate mail option: '", arg, "'");
291 conwriteln("twit_set: invalid args: ", origargs);
292 return;
294 hf.mail = arg.idup;
295 break;
296 case "url":
297 case "href":
298 if (hf.url.length != 0) {
299 conwriteln("twit_set: duplicate url option: '", arg, "'");
300 conwriteln("twit_set: invalid args: ", origargs);
301 return;
303 hf.url = arg.idup;
304 break;
305 default:
306 conwriteln("twit_set: unknown options: '", opt, "'");
307 conwriteln("twit_set: invalid args: ", origargs);
308 return;
311 if (hf.name.length == 0 && hf.mail.length == 0) { conwriteln("twit_set: invalid args (name/mail): ", origargs); return; }
312 if (hf.foldermask.length == 0) { conwriteln("twit_set: invalid args (no folder mask): ", origargs); return; }
313 if (hf.title is null) hf.title = "";
314 hitwlist ~= hf;
315 // HACK! this is to avoid false positives in GC
316 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
317 //conwriteln("new highlihgter: ", origargs);
318 ++hitwlistUpdateCounter;
319 postScreenRebuild();
320 })("twit_set", "twit nicks");
322 //twit_unset
323 // folder_mask glob
324 // name "name"
325 // mail "mail"
326 conRegFunc!((ConString[] args) {
327 auto origargs = args;
328 string fmask, name, mail;
329 while (args.length) {
330 if (args.length < 2) { conwriteln("twit_unset: invalid args: ", origargs); return; }
331 auto opt = args[0];
332 auto arg = args[1];
333 args = args[2..$];
334 switch (opt) {
335 case "foldermask":
336 case "folder_mask":
337 if (fmask.length != 0) {
338 conwriteln("twit_unset: duplicate folder mask option: '", arg, "'");
339 conwriteln("twit_unset: invalid args: ", origargs);
340 return;
342 fmask = arg.idup;
343 break;
344 case "name":
345 if (name.length != 0) {
346 conwriteln("twit_unset: duplicate name option: '", arg, "'");
347 conwriteln("twit_unset: invalid args: ", origargs);
348 return;
350 name = arg.idup;
351 break;
352 case "mail":
353 case "email":
354 case "e-mail":
355 if (mail.length != 0) {
356 conwriteln("twit_unset: duplicate mail option: '", arg, "'");
357 conwriteln("twit_unset: invalid args: ", origargs);
358 return;
360 mail = arg.idup;
361 break;
362 default:
363 conwriteln("twit_unset: unknown options: '", opt, "'");
364 conwriteln("twit_unset: invalid args: ", origargs);
365 return;
368 if (name.length == 0 && mail.length == 0) { conwriteln("twit_unset: invalid args (name/mail): ", origargs); return; }
369 //if (fmask.length == 0) { conwriteln("twit_unset: invalid args (no folder mask): ", origargs); return; }
370 for (int hidx = 0; hidx < hitwlist.length; ++hidx) {
371 if (auto twl = cast(TwitNick)hitwlist[hidx]) {
372 if (fmask.length && twl.foldermask != fmask) continue;
373 if (twl.name != name || twl.mail != mail) continue;
374 foreach (immutable cc; hidx+1..hitwlist.length) hitwlist.ptr[cc-1] = hitwlist.ptr[cc];
375 hitwlist[$-1] = null;
376 hitwlist.length -= 1;
377 hitwlist.assumeSafeAppend;
378 // HACK! this is to avoid false positives in GC
379 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
380 --hidx; // hack
383 ++hitwlistUpdateCounter;
384 postScreenRebuild();
385 })("twit_unset", "untwit nicks");
389 // ////////////////////////////////////////////////////////////////////////// //
390 public void twitThread (string folderPath, string msgid) {
391 string twcmd;
392 concmdfex!"twit_thread folder_mask \"%s\" msgid \"%s\""((ConString cc) { twcmd = cc.idup; }, folderPath, msgid);
393 try {
394 import std.path : buildPath;
395 auto fo = VFile(buildPath(mailRootDir, "auto_twit_threads.rc"), "a");
396 fo.writeln(twcmd.xstrip);
397 } catch (Exception e) {
398 conwriteln("ERROR saving thread twit: ", e.msg);