2011-12-16 [paul] 3.8.0cvs1
[claws.git] / tools / thunderbird-filters-convertor.pl
1 #!/usr/bin/perl -w
2
3 use strict;
4 use Getopt::Long;
5 use URI::Escape;
6
7 #  * This file is free software; you can redistribute it and/or modify it
8 #  * under the terms of the GNU General Public License as published by
9 #  * the Free Software Foundation; either version 3 of the License, or
10 #  * (at your option) any later version.
11 #  *
12 #  * This program is distributed in the hope that it will be useful, but
13 #  * WITHOUT ANY WARRANTY; without even the implied warranty of
14 #  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 #  * General Public License for more details.
16 #  *
17 #  * You should have received a copy of the GNU General Public License
18 #  * along with this program; if not, write to the Free Software
19 #  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #  *
21 #  * Copyright 2007 Paul Mangan <paul@claws-mail.org>
22 #  *
23
24 #
25 # Convert Thunderbird filtering rules to Claws Mail filtering rules
26 #
27
28 #
29 # TABLE OF EQUIVALENTS
30 #
31 # thunderbird                   :       Claws Mail
32 #------------------------------------------------------
33 #
34 # name="NAME"                   :       rulename "NAME"
35 #
36 # enabled="yes"                 :       enabled / disabled
37 #
38 # CONDITION LIST
39 # --------------
40 #
41 # OR                            :       |
42 # AND                           :       &
43 #
44 # subject                       :       subject
45 # from                          :       from
46 # to                            :       to
47 # cc                            :       cc
48 # to or cc                      :       to_or_cc
49 # body                          :       body-part
50 # date                          :       ****
51 # priority                      :       ****
52 # status                        :       ****
53 # age in days                   :       age_greater/age_lower
54 # size                          :       size_greater/size_smaller
55 # [custom header]               :       header
56 #
57 # 2nd level conditions
58 # --------------------
59 #
60 # contains                      :       [nothing]
61 # doesn't contain               :       [append with ~]
62 # is                            :       regexpcase
63 # isn't                         :       regexpcase
64 # ends with                     :       regexpcase
65 # begins with                   :       regexpcase
66 # is in ab                      :       found_in_addressbook
67 # isn't in ab                   :       ~found_in_addressbook
68 #
69 #
70 # status 2nd and 3rd level conditions
71 # -----------------------------------
72 #
73 # [is|isn't] replied
74 # [is|isn't] read
75 # [is|isn't] new
76 # [is|isn't] forwarded
77 # [is|isn't] flagged
78 #
79 #
80 # Date header 2nd  level condition
81 # --------------------------------
82 #
83 # is
84 # isn't
85 # is before
86 # is after
87 #
88 #
89 # Priority header 2nd and 3rd level conditions
90 # --------------------------------------------
91 # is             [Lowest|Low|Normal|High|Highest]
92 # is higher than [Lowest|Low|Normal|High|Highest]
93 # is lower than  [Lowest|Low|Normal|High|Highest]
94
95 #
96 # ACTION LIST
97 # -----------
98 #
99 # Move to folder                :       move
100 # Copy to folder                :       copy
101 # Forward                       :       ****
102 # Reply                         :       ****
103 # Mark read                     :       mark_as_read
104 # Mark flagged                  :       mark
105 # Label                         :       ****
106 # Change priority               :       ****
107 # JunkScore 100 [mark as spam]  :       ****
108 # JunkScore 0 [mark as ham]     :       ****
109 # Delete                        :       delete
110 # Delete from Pop3 server       :       delete
111 # Fetch body from Pop3Server    :       ****
112 #
113
114 my $script = "thunderbird-filters-convertor.pl";
115 my ($tbirdfile, $account, $mailbox, $iNeedHelp) = 0;
116
117 GetOptions("tbird-file=s"       => \$tbirdfile,
118            "account-name=s"     => \$account,
119            "mailbox-name=s"     => \$mailbox,
120            "help|h"             => \$iNeedHelp);
121
122 if ($iNeedHelp) {
123         help_me();
124 }
125
126 if (!$tbirdfile) {
127         print "ERROR: No filename given\n";
128         print "Use $script -h for help\n";
129         exit;
130 }
131
132 unless (-e $tbirdfile) {
133         print "ERROR: $tbirdfile NOT FOUND!!\n";
134         exit;
135 }
136
137 if (!$mailbox) {
138         print "ERROR: No mailbox name given\n";
139         print "Use $script -h for help\n";
140         exit;
141 }
142
143 my $config_dir = `claws-mail --config-dir` or die("ERROR:
144         You don't appear to have Claws Mail installed\n");
145 chomp $config_dir;
146
147 chdir($ENV{HOME} . "/$config_dir") or die("ERROR:
148         Claws Mail config directory not found [~/$config_dir]
149         You need to run Claws Mail once, quit it, and then re-run this script\n");
150
151 my $acrc = "accountrc";
152 my $acc_number;
153
154 if ($account) {
155         $acc_number = find_account_number();
156 }
157 if ($account && !$acc_number) {
158         print "ERROR: Account '$account' NOT FOUND!\n";
159         exit;
160 }
161
162 my @claws_filters = ();
163
164 ## check if matcherrc already exists
165 if (-e "matcherrc") {
166         print "matcherrc exists!\n";
167         read_current_filters();
168 } else {
169         push(@claws_filters, "[preglobal]\n\n[postglobal]\n\n[filtering]\n")
170 }
171 ##
172 my ($rule_count,@thunderbird_filters)  = read_thunderbird_filters();
173
174 my ($conv_rule,$ignored_rule,$ignore_list) = convert_filters($rule_count,@thunderbird_filters);
175
176 if (@claws_filters) {
177         system("mv matcherrc matcherrc-safecopy");
178         print "Moved ". $ENV{HOME}. "/$config_dir/matcherrc to "
179               . $ENV{HOME}. "/$config_dir/matcherrc-safecopy\n";
180 }
181 # write new config
182 open(MATCHERRC, ">>matcherrc");
183         print MATCHERRC @claws_filters;
184 close(MATCHERRC);
185
186 print "We're done!\n";
187 print "-------------\n";
188 print "Converted $conv_rule rules";
189 if (defined($ignored_rule)) {
190         print ", ignored $ignored_rule rules";
191 }
192 print "\n-------------\n";
193 print "$ignore_list";
194
195 exit;
196
197 sub help_me {
198         print<<'EOH';
199 Usage:  
200         thunderbird-filters-convertor.pl [options]
201 Options:
202         --help -h                       Show this screen.
203         --tbird-file=PATH TO FILE       The full path to the file to be converted
204         --mailbox-name=NAME             The name of the Claws Mail mailbox
205         --account-name=NAME             The name of the account to be used (optional)   
206 EOH
207 exit;
208 }
209
210 sub find_account_number {
211         my $cur_acc_numb;
212         my $cur_acc_name;
213
214         open (ACCOUNTRC, "<$acrc") || 
215                 die("Can't open the Accounts file [$acrc]\n");
216                 my @acrclines = <ACCOUNTRC>;
217         close ACCOUNTRC;
218         
219         foreach my $line (@acrclines) {
220                 unless ($line =~ m/^\[Account/ || 
221                         $line =~ m/^account_name/) { next; }
222                 chomp($line);
223
224                 if ($line =~ s/^\[Account: //) {
225                         $line =~ s/]$//;
226                         $cur_acc_numb = $line;
227                 }
228                 if ($line =~ s/^account_name=//) {
229                         $cur_acc_name = $line;
230                 }
231                 if (defined($cur_acc_name) && $cur_acc_name eq $account) {
232                         return($cur_acc_numb);
233                 }
234         }
235 }
236
237 sub read_current_filters {
238         print "Reading current filters\n";
239
240         open (CFILTERS, "<matcherrc") || 
241                 die("Can't open " . $ENV{HOME} . "/$config_dir/matcherrc");
242                 @claws_filters = <CFILTERS>;
243         close CFILTERS;
244
245         remove_last_empty_lines();
246 }
247
248 sub remove_last_empty_lines {
249         my $line = pop(@claws_filters);
250         if ($line =~ m/^$/) {
251                 remove_last_empty_lines();
252         } else {
253                 push(@claws_filters, $line);
254         }
255 }
256
257 sub read_thunderbird_filters {
258         my @outer_array = ();
259         my @inner_array = ();
260         my $count = 0;
261
262         open (TBIRDFILE, "<$tbirdfile") || 
263                 die("Can't open the tbird file [$tbirdfile]\n");
264                 my @tbirdlines = <TBIRDFILE>;
265         close TBIRDFILE;
266
267         foreach my $line (@tbirdlines) {
268                 if ($line =~ m/^version/ || $line =~ m/^logging/) { next; }
269
270                 chomp($line);
271
272                 push(@inner_array, "$line") unless $line eq "";
273                 if ($line =~ m/^condition/) {
274                         push(@outer_array, [@inner_array]);
275                         @inner_array = ();
276                         $count++;
277                 }
278         }
279         return($count-1,@outer_array);
280 }
281
282 sub convert_filters {
283         my ($rule_count,@thunderbird_filters) = @_;
284
285         my $tbird_action_no_value = qr/^(?:"Mark read"|"Mark flagged"|"Delete"|"Delete from Pop3 server"|"Fetch body from Pop3Server")$/;
286         my $tbird_action_ignore = qr/^(?:"Label"|"Change priority"|"JunkScore"|"Fetch body from Pop3Server"|"Delete from Pop3 server"|"Reply")$/;
287         my $exact_matches = qr/^(?:subject|from|to|cc)$/;
288         my $ignore_matches = qr/^(?:date|priority|status)$/;
289
290         my $conv_rules = my $ignored_rules = 0;
291         my $ignored_list = "";
292         for (my $outerloop = 0; $outerloop <= $rule_count; $outerloop++) {
293                 my $part_one = my $part_two = my $part_three = my $part_four = "";
294                 my $ignore_rule = my $move_rule = my $copy_rule = my $cond_count = 0;
295                 my %ignore_hash;
296                 my $bool = my $claws_condition = my $cur_name = "";
297                 for (my $innerloop = 0; exists($thunderbird_filters[$outerloop][$innerloop]); $innerloop++) {
298                         my $entry = $thunderbird_filters[$outerloop][$innerloop];
299                         if ($entry =~ s/^name=//) {
300                                 $cur_name = $entry;
301                                 $part_one = "rulename $entry ";
302                         } elsif ($entry =~ s/^enabled=//) {
303                                 if ($entry eq "\"yes\"") {
304                                         $part_one = "enabled $part_one";
305                                 } else {
306                                         $part_one = "disabled $part_one";
307                                 }
308                                 if (defined($acc_number)) {
309                                         $part_one .= "account $acc_number ";
310                                 }
311                         } elsif ($entry =~ s/^type=//) {
312                                 # do nothing : what does 'type' mean??
313                         } elsif ($entry =~ s/^action=//) {
314                                 if ($entry =~ m/$tbird_action_ignore/ && !$ignore_rule) {
315                                         $ignore_rule = 1;
316                                         unless ($ignore_hash{$cur_name}) {
317                                                 $ignored_list .= "Ignored $cur_name because it contains $entry\n";
318                                                 $ignored_rules++;
319                                         }
320                                         $ignore_hash{$cur_name}++;
321                                         $part_one = "";
322                                         next;
323                                 } elsif ($entry =~ m/Move to folder/) {
324                                         $part_four = "move ";
325                                         $move_rule = 1;
326                                 } elsif ($entry =~ m/Copy to folder/) {
327                                         $part_three .= "copy";
328                                         $copy_rule = 1;
329                                 } elsif ($entry =~ m/Mark read/) {
330                                         $part_three .= "mark_as_read ";
331                                 } elsif ($entry =~ m/Mark flagged/) {
332                                         $part_three .= "mark";
333                                 } elsif ($entry =~ m/Delete/) {
334                                         $part_three .= "delete";
335                                 }
336                         } elsif ($entry =~ s/^actionValue=//) {
337                                 if ($ignore_rule) {
338                                         $ignore_rule = 0;
339                                         next;
340                                 } elsif ($move_rule) {
341                                         $entry = rewrite_mailbox_name($entry);
342                                         $part_four .= uri_unescape($entry);
343                                         $move_rule = 0;                                 
344                                 } elsif ($copy_rule) {
345                                         $entry = rewrite_mailbox_name($entry);
346                                         $part_three .= " " . uri_unescape($entry);
347                                         $copy_rule = 0;
348                                 }
349                         } elsif ($entry =~ s/^condition=//) {
350                                 if ($entry =~ s/^\"AND//) {
351                                         $bool= "&";
352                                 } elsif ($entry =~ s/^\"OR//) {
353                                         $bool = "|";
354                                 }
355                                 my @tbird_conditions = split(/ \(/, $entry);
356                                 foreach my $cond (@tbird_conditions) {
357                                         my $exact = my $endswith = my $beginswith = my $addrbook = 0;
358                                         my $age_condition = my $size_condition = my $exact_age = 0;
359                                         $cond =~ s/\) OR$//;
360                                         $cond =~ s/\) AND$//;
361                                         $cond =~ s/\)"$//;
362                                         $cond =~ s/\\"/"/g;
363                                         my ($cpart_one, $cpart_two, $cpart_thr) = split(/,/, $cond, 3);
364                                         if ($cond) {
365                                                 if ($cpart_one =~ m/$exact_matches/) {
366                                                         $claws_condition .= "$cpart_one";
367                                                 } elsif ($cpart_one eq "to or cc") {
368                                                         $claws_condition .= "to_or_cc";
369                                                 } elsif ($cpart_one eq "body") {
370                                                         $claws_condition .= "body-part";
371                                                 } elsif ($cpart_one eq "age in days") {
372                                                         $age_condition = 1;
373                                                 } elsif ($cpart_one eq "size") {
374                                                         $size_condition = 1;
375                                                 } elsif ($cpart_one =~ m/$ignore_matches/) {
376                                                         $part_one = $claws_condition = $part_three = $part_four = "";
377                                                         next;
378                                                 } else {
379                                                         $claws_condition = "header $cpart_one";
380                                                 }
381
382                                                 if ($cpart_two eq "doesn't contain") {
383                                                         $claws_condition = "~$claws_condition matchcase";
384                                                 } elsif ($cpart_two eq "contains") {
385                                                         $claws_condition = "$claws_condition matchcase";
386                                                 } elsif ($cpart_two eq "isn't") {
387                                                         $exact = 1;
388                                                         $claws_condition = "~$claws_condition regexpcase";
389                                                 } elsif ($cpart_two eq "is") {
390                                                         if ($size_condition) {
391                                                                 $claws_condition .= "size_equal";
392                                                         } elsif ($age_condition) {
393                                                                 if ($bool ne "&") {
394                                                                         $part_one = $claws_condition = $part_three = $part_four = "";
395                                                                         if (!$ignored_list) {
396                                                                                 $ignored_list .= "Ignored $cur_name because it matches an exact age and is an OR match\n";
397                                                                         }
398                                                                         next;
399                                                                 } else {
400                                                                         $ignored_rules--;
401                                                                         $exact_age = 1;
402                                                                 }
403                                                         } else {
404                                                                 $exact = 1;
405                                                                 $claws_condition = "$claws_condition regexpcase";
406                                                         }
407                                                 } elsif ($cpart_two eq "ends with") {
408                                                         $endswith = 1;
409                                                         $claws_condition = "$claws_condition regexpcase";
410                                                 } elsif ($cpart_two eq "begins with") {
411                                                         $beginswith = 1;
412                                                         $claws_condition = "$claws_condition regexpcase";
413                                                 } elsif ($cpart_two eq "is in ab") {
414                                                         $addrbook = 1;
415                                                         $claws_condition = "found_in_addressbook \"$claws_condition\" in \"Any\" ";
416                                                 } elsif ($cpart_two eq "isn't in ab") {
417                                                         $addrbook = 1;
418                                                         $claws_condition = "~found_in_addressbook \"$claws_condition\" in \"Any\" ";
419                                                 } elsif ($cpart_two eq "is greater than") {
420                                                         if ($size_condition) {
421                                                                 $claws_condition .= "size_greater";
422                                                         }
423                                                         if ($age_condition) {
424                                                                 $claws_condition .= "age_greater";
425                                                         }
426                                                 } elsif ($cpart_two eq "is less than") {
427                                                         if ($size_condition) {
428                                                                 $claws_condition .= "size_smaller";
429                                                         }
430                                                         if ($age_condition) {
431                                                                 $claws_condition .= "age_lower";
432                                                         }
433                                                 }                                                       
434                                 
435                                                 if ($exact || $beginswith || $endswith) {
436                                                         $cpart_thr = escape_regex($cpart_thr);
437                                                 }
438                                                 if ($exact) {
439                                                         $cpart_thr = "^$cpart_thr\$";
440                                                 } elsif ($beginswith) {
441                                                         $cpart_thr = "^$cpart_thr";
442                                                 } elsif ($endswith) {
443                                                         $cpart_thr = "$cpart_thr\$";
444                                                 }
445                                                 unless ($addrbook) {
446                                                         if ($exact_age) {
447                                                                 my $lower_limit = $cpart_thr-1;
448                                                                 my $upper_limit = $cpart_thr+1;
449                                                                 $lower_limit =~ s/^\"//;
450                                                                 $lower_limit =~ s/\"$//;
451                                                                 $upper_limit =~ s/^\"//;
452                                                                 $upper_limit =~ s/\"$//;
453                                                                 $claws_condition = "$claws_condition"."age_lower"
454                                                                                          . " $upper_limit $bool "
455                                                                                          . "age_greater $lower_limit ";
456                                                         } elsif ($size_condition || $age_condition) {
457                                                                 $claws_condition = "$claws_condition $cpart_thr ";
458                                                         } else {        
459                                                                 $claws_condition = "$claws_condition \"$cpart_thr\" ";
460                                                         }
461                                                 }
462
463                                                 if ($tbird_conditions[1] && $cond_count < $#tbird_conditions) {
464                                                         $claws_condition = "$claws_condition$bool ";
465                                                 }
466                                         }
467                                         $cond_count++;
468                                 }
469                                 if ($part_one) {
470                                         $conv_rules++;
471                                         push(@claws_filters, "$part_one$claws_condition$part_three$part_four\n");
472                                 }
473                         }
474                 }
475         }
476         push(@claws_filters, "\n");
477         return($conv_rules,$ignored_rules,$ignored_list);
478 }
479
480 sub rewrite_mailbox_name {
481         my ($path) = @_;
482
483         my $new_path;
484
485         my ($front,$back) = split(/\/\//, $path, 2);
486
487         if ($front =~ m/^"mailbox/) {
488                 $new_path = "\"#mh/$mailbox/";
489         } else {
490                 $new_path = "\"#imap/$mailbox/";
491         }
492
493         my ($box,$name) = split(/\//, $back, 2);
494
495         if ($new_path =~ m/^"#mh/) {
496                 $name =~ s/^Inbox/inbox/;
497                 $name =~ s/^Sent/sent/;
498                 $name =~ s/^Drafts/draft/;
499                 $name =~ s/^Trash/trash/;
500         }
501         $new_path = $new_path.$name;
502
503         return($new_path);
504 }
505
506 sub escape_regex {
507         my ($string) = @_;
508
509         my $escstr = "";
510         my $symbols = qr/^(?:\[|\]|\{|\}|\(|\)|\||\+|\*|\.|\-|\$|\^)$/;
511         my @chars = split(//, $string);
512
513         foreach my $char (@chars) {
514                 if ($char =~ m/$symbols/) { $char = "\\\\$char"; }
515                 $escstr .= $char;
516         }
517
518         return($escstr);
519 }