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*/;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public abstract class Highlighter
{
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
{
48 bool anchorStart
, anchorEnd
;
50 override bool hiCheck (Folder
fld, Article art
, uint aidx
, ArticleBase abase
) nothrow {
51 if (art
is null) return false;
53 string cs
= art
.frommail
;
54 if (pat
.length
== 0) return false;
56 if (anchorEnd
&& cs
.length
!= pat
.length
) return false;
57 if (cs
.startsWithCI(pat
)) return true;
58 } else if (anchorEnd
) {
60 if (cs
.length
< pat
.length
) return false;
61 if (cs
[$-pat
.length
..$].strEquCI(pat
)) return true;
65 while (cs
.length
>= pat
.length
) {
66 if (cs
.startsWithCI(pat
)) return true;
73 override string
twitCheck (Folder
fld, Article art
, uint aidx
, ArticleBase abase
) nothrow { return null; }
77 final class TwitThread
: Highlighter
{
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 "";
95 final class TwitNick
: Highlighter
{
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
123 if (aidx
== 0) break;
127 // check for myself in this thread
129 while (art
!is null) {
130 if (isMine(art
)) return null; // nope, i replied there
132 if (aidx
== 0) break;
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;
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; }
169 if (hf
.email
.length
!= 0) {
170 conwriteln("highlight: duplicate email option: '", arg
, "'");
171 conwriteln("highlight: invalid args: ", origargs
);
178 if (hf
.foldermask
.length
!= 0) {
179 conwriteln("highlight: duplicate folder mask option: '", arg
, "'");
180 conwriteln("highlight: invalid args: ", origargs
);
183 hf
.foldermask
= arg
.idup
;
186 conwriteln("highlight: unknown options: '", opt
, "'");
187 conwriteln("highlight: invalid args: ", origargs
);
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; }
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
;
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; }
215 if (hf
.foldermask
.length
!= 0) {
216 conwriteln("twit: duplicate folder mask option: '", arg
, "'");
217 conwriteln("twit: invalid args: ", origargs
);
220 hf
.foldermask
= arg
.idup
;
226 if (hf
.msgid
.length
!= 0) {
227 conwriteln("twit: duplicate message id option: '", arg
, "'");
228 conwriteln("twit: invalid args: ", origargs
);
234 conwriteln("twit: unknown options: '", opt
, "'");
235 conwriteln("twit: invalid args: ", origargs
);
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; }
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
;
247 })("twit_thread", "twit threads");
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; }
267 if (hf
.foldermask
.length
!= 0) {
268 conwriteln("twit_set: duplicate folder mask option: '", arg
, "'");
269 conwriteln("twit_set: invalid args: ", origargs
);
272 hf
.foldermask
= arg
.idup
;
278 if (hf
.msgid
.length
!= 0) {
279 conwriteln("twit_set: duplicate message id option: '", arg
, "'");
280 conwriteln("twit_set: invalid args: ", origargs
);
286 if (hf
.title
!is null) {
287 conwriteln("twit_set: duplicate title option: '", arg
, "'");
288 conwriteln("twit_set: invalid args: ", origargs
);
291 hf
.title
= (arg
.length ? arg
.idup
: "");
294 if (hf
.name
.length
!= 0) {
295 conwriteln("twit_set: duplicate name option: '", arg
, "'");
296 conwriteln("twit_set: invalid args: ", origargs
);
304 if (hf
.mail
.length
!= 0) {
305 conwriteln("twit_set: duplicate mail option: '", arg
, "'");
306 conwriteln("twit_set: invalid args: ", origargs
);
313 if (hf
.url
.length
!= 0) {
314 conwriteln("twit_set: duplicate url option: '", arg
, "'");
315 conwriteln("twit_set: invalid args: ", origargs
);
321 conwriteln("twit_set: unknown options: '", opt
, "'");
322 conwriteln("twit_set: invalid args: ", origargs
);
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
= "";
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
;
335 })("twit_set", "twit nicks");
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; }
352 if (fmask
.length
!= 0) {
353 conwriteln("twit_unset: duplicate folder mask option: '", arg
, "'");
354 conwriteln("twit_unset: invalid args: ", origargs
);
360 if (name
.length
!= 0) {
361 conwriteln("twit_unset: duplicate name option: '", arg
, "'");
362 conwriteln("twit_unset: invalid args: ", origargs
);
370 if (mail
.length
!= 0) {
371 conwriteln("twit_unset: duplicate mail option: '", arg
, "'");
372 conwriteln("twit_unset: invalid args: ", origargs
);
378 conwriteln("twit_unset: unknown options: '", opt
, "'");
379 conwriteln("twit_unset: invalid args: ", origargs
);
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); }
398 ++hitwlistUpdateCounter
;
400 })("twit_unset", "untwit nicks");
404 // ////////////////////////////////////////////////////////////////////////// //
405 public void twitThread (string folderPath
, string msgid
) {
407 concmdfdg("twit_thread folder_mask \"%s\" msgid \"%s\"", (ConString cc
) { twcmd
= cc
.idup
; }, folderPath
, msgid
);
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
);