sqlite: accept "binary" encoding (idiotic lj sends this sometimes)
[chiroptera.git] / hitwit.d
blobd98ca36bdb470108b7068c194a61ef82c5d04ed5
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, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module hitwit /*is aliced*/;
18 private:
20 import iv.alice;
21 import iv.cmdcon;
22 import iv.strex;
23 import iv.vfs.io;
25 import egui;
26 import maildb;
28 import folder;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public abstract class Highlighter {
33 string foldermask;
35 bool isOurFolder (Folder fld) nothrow {
36 import std.path : globMatch;
37 if (fld is null) return false;
38 return globMatch(fld.folderPath, foldermask);
41 bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow; // no need to check folder here, it is passed just for informational purposes
42 string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow; // no need to check folder here, it is passed just for informational purposes
46 final class HighlightFrom : Highlighter {
47 string email;
48 bool anchorStart, anchorEnd;
50 override bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow {
51 if (art is null) return false;
52 string pat = email;
53 string cs = art.frommail;
54 if (pat.length == 0) return false;
55 if (anchorStart) {
56 if (anchorEnd && cs.length != pat.length) return false;
57 if (cs.startsWithCI(pat)) return true;
58 } else if (anchorEnd) {
59 assert(!anchorStart);
60 if (cs.length < pat.length) return false;
61 if (cs[$-pat.length..$].strEquCI(pat)) return true;
62 } else {
63 assert(!anchorStart);
64 assert(!anchorEnd);
65 while (cs.length >= pat.length) {
66 if (cs.startsWithCI(pat)) return true;
67 cs = cs[1..$];
70 return false;
73 override string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow { return null; }
77 final class TwitThread : Highlighter {
78 string msgid;
80 override bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow { return false; }
82 override string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow {
83 if (msgid.length == 0) return null;
84 while (art !is null) {
85 if (art.msgid == msgid) return "";
86 aidx = art.parent;
87 if (aidx == 0) break;
88 art = abase[aidx];
90 return null;
95 final class TwitNick : Highlighter {
96 string name;
97 string mail;
98 string title;
99 string msgid;
100 string url;
102 private final string checkArt (Article art) nothrow @nogc {
103 if (art is null) return null;
104 if (name.length == 0 && mail.length == 0) return null;
105 if (name.length && art.fromname != name) return null;
106 if (mail.length && !art.frommail.strEquCI(mail)) return null;
107 return (title !is null ? title : "");
110 override bool hiCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow { return false; }
112 final bool isMine (Article art) nothrow @nogc {
113 if (art is null) return false; // just in case
114 return (art.fromname.indexOf("ketmar") >= 0 || art.fromname.indexOf("Ketmar") >= 0);
117 //FIXME: remove hardcoded strings, move 'em to configs
118 override string twitCheck (Folder fld, Article art, uint aidx, ArticleBase abase) nothrow {
119 auto res = checkArt(art);
120 while (res is null && art !is null) {
121 if (isMine(art)) return null; // nope, i replied there
122 aidx = art.parent;
123 if (aidx == 0) break;
124 art = abase[aidx];
125 res = checkArt(art);
127 // check for myself in this thread
128 if (res !is null) {
129 while (art !is null) {
130 if (isMine(art)) return null; // nope, i replied there
131 aidx = art.parent;
132 if (aidx == 0) break;
133 art = abase[aidx];
136 return res;
140 private __gshared Highlighter[] hitwlist;
141 public __gshared uint hitwlistUpdateCounter;
144 // ////////////////////////////////////////////////////////////////////////// //
145 public bool forEachHitTwit (scope bool delegate (Highlighter ht) nothrow dg) nothrow {
146 if (dg is null) return false;
147 foreach (Highlighter hg; hitwlist) {
148 if (dg(hg)) return true;
150 return false;
154 // ////////////////////////////////////////////////////////////////////////// //
155 // cannot use module ctor due to cyclic dependencies
156 public void hitwitInitConsole () {
157 //highlight fromemail ^ketmar@ketmar.no-ip.org$ folder_mask dmars_ng/*
158 conRegFunc!((ConString[] args) {
159 auto origargs = args;
160 auto hf = new HighlightFrom();
161 while (args.length) {
162 if (args.length < 2) { conwriteln("highlight: invalid args: ", origargs); return; }
163 auto opt = args[0];
164 auto arg = args[1];
165 args = args[2..$];
166 switch (opt) {
167 case "fromemail":
168 case "from_email":
169 if (hf.email.length != 0) {
170 conwriteln("highlight: duplicate email option: '", arg, "'");
171 conwriteln("highlight: invalid args: ", origargs);
172 return;
174 hf.email = arg.idup;
175 break;
176 case "foldermask":
177 case "folder_mask":
178 if (hf.foldermask.length != 0) {
179 conwriteln("highlight: duplicate folder mask option: '", arg, "'");
180 conwriteln("highlight: invalid args: ", origargs);
181 return;
183 hf.foldermask = arg.idup;
184 break;
185 default:
186 conwriteln("highlight: unknown options: '", opt, "'");
187 conwriteln("highlight: invalid args: ", origargs);
188 return;
191 if (hf.email.length && hf.email[0] == '^') { hf.email = hf.email[1..$]; hf.anchorStart = true; }
192 if (hf.email.length && hf.email[$-1] == '$') { hf.email = hf.email[0..$-1]; hf.anchorEnd = true; }
193 if (hf.email.length == 0) { conwriteln("highlight: invalid args (no email): ", origargs); return; }
194 if (hf.foldermask.length == 0) { conwriteln("highlight: invalid args (no folder mask): ", origargs); return; }
195 hitwlist ~= hf;
196 // HACK! this is to avoid false positives in GC
197 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
198 //conwriteln("new highlihgter: ", origargs);
199 ++hitwlistUpdateCounter;
200 postScreenRebuild();
201 })("highlight", "highight messages in thread pane");
203 //twit_thread folder_mask dmars_ng/* msgid <nsp82c$t2g$1@digitalmars.com>
204 conRegFunc!((ConString[] args) {
205 auto origargs = args;
206 auto hf = new TwitThread();
207 while (args.length) {
208 if (args.length < 2) { conwriteln("twit: invalid args: ", origargs); return; }
209 auto opt = args[0];
210 auto arg = args[1];
211 args = args[2..$];
212 switch (opt) {
213 case "foldermask":
214 case "folder_mask":
215 if (hf.foldermask.length != 0) {
216 conwriteln("twit: duplicate folder mask option: '", arg, "'");
217 conwriteln("twit: invalid args: ", origargs);
218 return;
220 hf.foldermask = arg.idup;
221 break;
222 case "msgid":
223 case "message":
224 case "messageid":
225 case "message_id":
226 if (hf.msgid.length != 0) {
227 conwriteln("twit: duplicate message id option: '", arg, "'");
228 conwriteln("twit: invalid args: ", origargs);
229 return;
231 hf.msgid = arg.idup;
232 break;
233 default:
234 conwriteln("twit: unknown options: '", opt, "'");
235 conwriteln("twit: invalid args: ", origargs);
236 return;
239 if (hf.msgid.length == 0) { conwriteln("twit: invalid args (no msgid): ", origargs); return; }
240 if (hf.foldermask.length == 0) { conwriteln("twit: invalid args (no folder mask): ", origargs); return; }
241 hitwlist ~= hf;
242 // HACK! this is to avoid false positives in GC
243 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
244 //conwriteln("new highlihgter: ", origargs);
245 ++hitwlistUpdateCounter;
246 postScreenRebuild();
247 })("twit_thread", "twit threads");
249 //twit_set
250 // folder_mask glob
251 // title "title"
252 // name "name"
253 // mail "mail"
254 // message "msgid"
255 // url "url"
256 conRegFunc!((ConString[] args) {
257 auto origargs = args;
258 auto hf = new TwitNick();
259 while (args.length) {
260 if (args.length < 2) { conwriteln("twit_set: invalid args: ", origargs); return; }
261 auto opt = args[0];
262 auto arg = args[1];
263 args = args[2..$];
264 switch (opt) {
265 case "foldermask":
266 case "folder_mask":
267 if (hf.foldermask.length != 0) {
268 conwriteln("twit_set: duplicate folder mask option: '", arg, "'");
269 conwriteln("twit_set: invalid args: ", origargs);
270 return;
272 hf.foldermask = arg.idup;
273 break;
274 case "msgid":
275 case "message":
276 case "messageid":
277 case "message_id":
278 if (hf.msgid.length != 0) {
279 conwriteln("twit_set: duplicate message id option: '", arg, "'");
280 conwriteln("twit_set: invalid args: ", origargs);
281 return;
283 hf.msgid = arg.idup;
284 break;
285 case "title":
286 if (hf.title !is null) {
287 conwriteln("twit_set: duplicate title option: '", arg, "'");
288 conwriteln("twit_set: invalid args: ", origargs);
289 return;
291 hf.title = (arg.length ? arg.idup : "");
292 break;
293 case "name":
294 if (hf.name.length != 0) {
295 conwriteln("twit_set: duplicate name option: '", arg, "'");
296 conwriteln("twit_set: invalid args: ", origargs);
297 return;
299 hf.name = arg.idup;
300 break;
301 case "mail":
302 case "email":
303 case "e-mail":
304 if (hf.mail.length != 0) {
305 conwriteln("twit_set: duplicate mail option: '", arg, "'");
306 conwriteln("twit_set: invalid args: ", origargs);
307 return;
309 hf.mail = arg.idup;
310 break;
311 case "url":
312 case "href":
313 if (hf.url.length != 0) {
314 conwriteln("twit_set: duplicate url option: '", arg, "'");
315 conwriteln("twit_set: invalid args: ", origargs);
316 return;
318 hf.url = arg.idup;
319 break;
320 default:
321 conwriteln("twit_set: unknown options: '", opt, "'");
322 conwriteln("twit_set: invalid args: ", origargs);
323 return;
326 if (hf.name.length == 0 && hf.mail.length == 0) { conwriteln("twit_set: invalid args (name/mail): ", origargs); return; }
327 if (hf.foldermask.length == 0) { conwriteln("twit_set: invalid args (no folder mask): ", origargs); return; }
328 if (hf.title is null) hf.title = "";
329 hitwlist ~= hf;
330 // HACK! this is to avoid false positives in GC
331 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
332 //conwriteln("new highlihgter: ", origargs);
333 ++hitwlistUpdateCounter;
334 postScreenRebuild();
335 })("twit_set", "twit nicks");
337 //twit_unset
338 // folder_mask glob
339 // name "name"
340 // mail "mail"
341 conRegFunc!((ConString[] args) {
342 auto origargs = args;
343 string fmask, name, mail;
344 while (args.length) {
345 if (args.length < 2) { conwriteln("twit_unset: invalid args: ", origargs); return; }
346 auto opt = args[0];
347 auto arg = args[1];
348 args = args[2..$];
349 switch (opt) {
350 case "foldermask":
351 case "folder_mask":
352 if (fmask.length != 0) {
353 conwriteln("twit_unset: duplicate folder mask option: '", arg, "'");
354 conwriteln("twit_unset: invalid args: ", origargs);
355 return;
357 fmask = arg.idup;
358 break;
359 case "name":
360 if (name.length != 0) {
361 conwriteln("twit_unset: duplicate name option: '", arg, "'");
362 conwriteln("twit_unset: invalid args: ", origargs);
363 return;
365 name = arg.idup;
366 break;
367 case "mail":
368 case "email":
369 case "e-mail":
370 if (mail.length != 0) {
371 conwriteln("twit_unset: duplicate mail option: '", arg, "'");
372 conwriteln("twit_unset: invalid args: ", origargs);
373 return;
375 mail = arg.idup;
376 break;
377 default:
378 conwriteln("twit_unset: unknown options: '", opt, "'");
379 conwriteln("twit_unset: invalid args: ", origargs);
380 return;
383 if (name.length == 0 && mail.length == 0) { conwriteln("twit_unset: invalid args (name/mail): ", origargs); return; }
384 //if (fmask.length == 0) { conwriteln("twit_unset: invalid args (no folder mask): ", origargs); return; }
385 for (int hidx = 0; hidx < hitwlist.length; ++hidx) {
386 if (auto twl = cast(TwitNick)hitwlist[hidx]) {
387 if (fmask.length && twl.foldermask != fmask) continue;
388 if (twl.name != name || twl.mail != mail) continue;
389 foreach (immutable cc; hidx+1..hitwlist.length) hitwlist.ptr[cc-1] = hitwlist.ptr[cc];
390 hitwlist[$-1] = null;
391 hitwlist.length -= 1;
392 hitwlist.assumeSafeAppend;
393 // HACK! this is to avoid false positives in GC
394 //{ import core.memory : GC; GC.setAttr(hitwlist.ptr, GC.BlkAttr.NO_SCAN|GC.BlkAttr.NO_INTERIOR); }
395 --hidx; // hack
398 ++hitwlistUpdateCounter;
399 postScreenRebuild();
400 })("twit_unset", "untwit nicks");
404 // ////////////////////////////////////////////////////////////////////////// //
405 public void twitThread (string folderPath, string msgid) {
406 string twcmd;
407 concmdfdg("twit_thread folder_mask \"%s\" msgid \"%s\"", (ConString cc) { twcmd = cc.idup; }, folderPath, msgid);
408 try {
409 import std.path : buildPath;
410 auto fo = VFile(buildPath(mailRootDir, "auto_twit_threads.rc"), "a");
411 fo.writeln(twcmd.xstrip);
412 } catch (Exception e) {
413 conwriteln("ERROR saving thread twit: ", e.msg);