123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- #!/usr/bin/perl -w
- use strict;
- use Getopt::Long;
- use URI::Escape;
- # * This file is free software; you can redistribute it and/or modify it
- # * under the terms of the GNU General Public License as published by
- # * the Free Software Foundation; either version 3 of the License, or
- # * (at your option) any later version.
- # *
- # * This program is distributed in the hope that it will be useful, but
- # * WITHOUT ANY WARRANTY; without even the implied warranty of
- # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # * General Public License for more details.
- # *
- # * You should have received a copy of the GNU General Public License
- # * along with this program; if not, write to the Free Software
- # * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- # *
- # * Copyright 2007 Paul Mangan <paul@claws-mail.org>
- # *
- #
- # Convert Thunderbird filtering rules to Claws Mail filtering rules
- #
- #
- # TABLE OF EQUIVALENTS
- #
- # thunderbird : Claws Mail
- #------------------------------------------------------
- #
- # name="NAME" : rulename "NAME"
- #
- # enabled="yes" : enabled / disabled
- #
- # CONDITION LIST
- # --------------
- #
- # OR : |
- # AND : &
- #
- # subject : subject
- # from : from
- # to : to
- # cc : cc
- # to or cc : to_or_cc
- # body : body-part
- # date : ****
- # priority : ****
- # status : ****
- # age in days : age_greater/age_lower
- # size : size_greater/size_smaller
- # [custom header] : header
- #
- # 2nd level conditions
- # --------------------
- #
- # contains : [nothing]
- # doesn't contain : [append with ~]
- # is : regexpcase
- # isn't : regexpcase
- # ends with : regexpcase
- # begins with : regexpcase
- # is in ab : found_in_addressbook
- # isn't in ab : ~found_in_addressbook
- #
- #
- # status 2nd and 3rd level conditions
- # -----------------------------------
- #
- # [is|isn't] replied
- # [is|isn't] read
- # [is|isn't] new
- # [is|isn't] forwarded
- # [is|isn't] flagged
- #
- #
- # Date header 2nd level condition
- # --------------------------------
- #
- # is
- # isn't
- # is before
- # is after
- #
- #
- # Priority header 2nd and 3rd level conditions
- # --------------------------------------------
- # is [Lowest|Low|Normal|High|Highest]
- # is higher than [Lowest|Low|Normal|High|Highest]
- # is lower than [Lowest|Low|Normal|High|Highest]
- #
- #
- # ACTION LIST
- # -----------
- #
- # Move to folder : move
- # Copy to folder : copy
- # Forward : ****
- # Reply : ****
- # Mark read : mark_as_read
- # Mark flagged : mark
- # Label : ****
- # Change priority : ****
- # JunkScore 100 [mark as spam] : ****
- # JunkScore 0 [mark as ham] : ****
- # Delete : delete
- # Delete from Pop3 server : delete
- # Fetch body from Pop3Server : ****
- #
- my $script = "thunderbird-filters-convertor.pl";
- my ($tbirdfile, $account, $mailbox, $iNeedHelp) = 0;
- GetOptions("tbird-file=s" => \$tbirdfile,
- "account-name=s" => \$account,
- "mailbox-name=s" => \$mailbox,
- "help|h" => \$iNeedHelp);
- if ($iNeedHelp) {
- help_me();
- }
- if (!$tbirdfile) {
- print "ERROR: No filename given\n";
- print "Use $script -h for help\n";
- exit;
- }
- unless (-e $tbirdfile) {
- print "ERROR: $tbirdfile NOT FOUND!!\n";
- exit;
- }
- if (!$mailbox) {
- print "ERROR: No mailbox name given\n";
- print "Use $script -h for help\n";
- exit;
- }
- my $config_dir = `claws-mail --config-dir` or die("ERROR:
- You don't appear to have Claws Mail installed\n");
- chomp $config_dir;
- chdir($ENV{HOME} . "/$config_dir") or die("ERROR:
- Claws Mail config directory not found [~/$config_dir]
- You need to run Claws Mail once, quit it, and then re-run this script\n");
- my $acrc = "accountrc";
- my $acc_number;
- if ($account) {
- $acc_number = find_account_number();
- }
- if ($account && !$acc_number) {
- print "ERROR: Account '$account' NOT FOUND!\n";
- exit;
- }
- my @claws_filters = ();
- ## check if matcherrc already exists
- if (-e "matcherrc") {
- print "matcherrc exists!\n";
- read_current_filters();
- } else {
- push(@claws_filters, "[preglobal]\n\n[postglobal]\n\n[filtering]\n")
- }
- ##
- my ($rule_count,@thunderbird_filters) = read_thunderbird_filters();
- my ($conv_rule,$ignored_rule,$ignore_list) = convert_filters($rule_count,@thunderbird_filters);
- if (@claws_filters) {
- system("mv matcherrc matcherrc-safecopy");
- print "Moved ". $ENV{HOME}. "/$config_dir/matcherrc to "
- . $ENV{HOME}. "/$config_dir/matcherrc-safecopy\n";
- }
- # write new config
- open(MATCHERRC, ">>matcherrc");
- print MATCHERRC @claws_filters;
- close(MATCHERRC);
- print "We're done!\n";
- print "-------------\n";
- print "Converted $conv_rule rules";
- if (defined($ignored_rule)) {
- print ", ignored $ignored_rule rules";
- }
- print "\n-------------\n";
- print "$ignore_list";
- exit;
- sub help_me {
- print<<'EOH';
- Usage:
- thunderbird-filters-convertor.pl [options]
- Options:
- --help -h Show this screen.
- --tbird-file=PATH TO FILE The full path to the file to be converted
- --mailbox-name=NAME The name of the Claws Mail mailbox
- --account-name=NAME The name of the account to be used (optional)
- EOH
- exit;
- }
- sub find_account_number {
- my $cur_acc_numb;
- my $cur_acc_name;
- open (ACCOUNTRC, "<$acrc") ||
- die("Can't open the Accounts file [$acrc]\n");
- my @acrclines = <ACCOUNTRC>;
- close ACCOUNTRC;
-
- foreach my $line (@acrclines) {
- unless ($line =~ m/^\[Account/ ||
- $line =~ m/^account_name/) { next; }
- chomp($line);
- if ($line =~ s/^\[Account: //) {
- $line =~ s/]$//;
- $cur_acc_numb = $line;
- }
- if ($line =~ s/^account_name=//) {
- $cur_acc_name = $line;
- }
- if (defined($cur_acc_name) && $cur_acc_name eq $account) {
- return($cur_acc_numb);
- }
- }
- }
- sub read_current_filters {
- print "Reading current filters\n";
- open (CFILTERS, "<matcherrc") ||
- die("Can't open " . $ENV{HOME} . "/$config_dir/matcherrc");
- @claws_filters = <CFILTERS>;
- close CFILTERS;
- remove_last_empty_lines();
- }
- sub remove_last_empty_lines {
- my $line = pop(@claws_filters);
- if ($line =~ m/^$/) {
- remove_last_empty_lines();
- } else {
- push(@claws_filters, $line);
- }
- }
- sub read_thunderbird_filters {
- my @outer_array = ();
- my @inner_array = ();
- my $count = 0;
- open (TBIRDFILE, "<$tbirdfile") ||
- die("Can't open the tbird file [$tbirdfile]\n");
- my @tbirdlines = <TBIRDFILE>;
- close TBIRDFILE;
- foreach my $line (@tbirdlines) {
- if ($line =~ m/^version/ || $line =~ m/^logging/) { next; }
- chomp($line);
- push(@inner_array, "$line") unless $line eq "";
- if ($line =~ m/^condition/) {
- push(@outer_array, [@inner_array]);
- @inner_array = ();
- $count++;
- }
- }
- return($count-1,@outer_array);
- }
- sub convert_filters {
- my ($rule_count,@thunderbird_filters) = @_;
- my $tbird_action_no_value = qr/^(?:"Mark read"|"Mark flagged"|"Delete"|"Delete from Pop3 server"|"Fetch body from Pop3Server")$/;
- my $tbird_action_ignore = qr/^(?:"Label"|"Change priority"|"JunkScore"|"Fetch body from Pop3Server"|"Delete from Pop3 server"|"Reply")$/;
- my $exact_matches = qr/^(?:subject|from|to|cc)$/;
- my $ignore_matches = qr/^(?:date|priority|status)$/;
- my $conv_rules = my $ignored_rules = 0;
- my $ignored_list = "";
- for (my $outerloop = 0; $outerloop <= $rule_count; $outerloop++) {
- my $part_one = my $part_two = my $part_three = my $part_four = "";
- my $ignore_rule = my $move_rule = my $copy_rule = my $cond_count = 0;
- my %ignore_hash;
- my $bool = my $claws_condition = my $cur_name = "";
- for (my $innerloop = 0; exists($thunderbird_filters[$outerloop][$innerloop]); $innerloop++) {
- my $entry = $thunderbird_filters[$outerloop][$innerloop];
- if ($entry =~ s/^name=//) {
- $cur_name = $entry;
- $part_one = "rulename $entry ";
- } elsif ($entry =~ s/^enabled=//) {
- if ($entry eq "\"yes\"") {
- $part_one = "enabled $part_one";
- } else {
- $part_one = "disabled $part_one";
- }
- if (defined($acc_number)) {
- $part_one .= "account $acc_number ";
- }
- } elsif ($entry =~ s/^type=//) {
- # do nothing : what does 'type' mean??
- } elsif ($entry =~ s/^action=//) {
- if ($entry =~ m/$tbird_action_ignore/ && !$ignore_rule) {
- $ignore_rule = 1;
- unless ($ignore_hash{$cur_name}) {
- $ignored_list .= "Ignored $cur_name because it contains $entry\n";
- $ignored_rules++;
- }
- $ignore_hash{$cur_name}++;
- $part_one = "";
- next;
- } elsif ($entry =~ m/Move to folder/) {
- $part_four = "move ";
- $move_rule = 1;
- } elsif ($entry =~ m/Copy to folder/) {
- $part_three .= "copy";
- $copy_rule = 1;
- } elsif ($entry =~ m/Mark read/) {
- $part_three .= "mark_as_read ";
- } elsif ($entry =~ m/Mark flagged/) {
- $part_three .= "mark";
- } elsif ($entry =~ m/Delete/) {
- $part_three .= "delete";
- }
- } elsif ($entry =~ s/^actionValue=//) {
- if ($ignore_rule) {
- $ignore_rule = 0;
- next;
- } elsif ($move_rule) {
- $entry = rewrite_mailbox_name($entry);
- $part_four .= uri_unescape($entry);
- $move_rule = 0;
- } elsif ($copy_rule) {
- $entry = rewrite_mailbox_name($entry);
- $part_three .= " " . uri_unescape($entry);
- $copy_rule = 0;
- }
- } elsif ($entry =~ s/^condition=//) {
- if ($entry =~ s/^\"AND//) {
- $bool= "&";
- } elsif ($entry =~ s/^\"OR//) {
- $bool = "|";
- }
- my @tbird_conditions = split(/ \(/, $entry);
- foreach my $cond (@tbird_conditions) {
- my $exact = my $endswith = my $beginswith = my $addrbook = 0;
- my $age_condition = my $size_condition = my $exact_age = 0;
- $cond =~ s/\) OR$//;
- $cond =~ s/\) AND$//;
- $cond =~ s/\)"$//;
- $cond =~ s/\\"/"/g;
- my ($cpart_one, $cpart_two, $cpart_thr) = split(/,/, $cond, 3);
- if ($cond) {
- if ($cpart_one =~ m/$exact_matches/) {
- $claws_condition .= "$cpart_one";
- } elsif ($cpart_one eq "to or cc") {
- $claws_condition .= "to_or_cc";
- } elsif ($cpart_one eq "body") {
- $claws_condition .= "body-part";
- } elsif ($cpart_one eq "age in days") {
- $age_condition = 1;
- } elsif ($cpart_one eq "size") {
- $size_condition = 1;
- } elsif ($cpart_one =~ m/$ignore_matches/) {
- $part_one = $claws_condition = $part_three = $part_four = "";
- next;
- } else {
- $claws_condition = "header $cpart_one";
- }
- if ($cpart_two eq "doesn't contain") {
- $claws_condition = "~$claws_condition matchcase";
- } elsif ($cpart_two eq "contains") {
- $claws_condition = "$claws_condition matchcase";
- } elsif ($cpart_two eq "isn't") {
- $exact = 1;
- $claws_condition = "~$claws_condition regexpcase";
- } elsif ($cpart_two eq "is") {
- if ($size_condition) {
- $claws_condition .= "size_equal";
- } elsif ($age_condition) {
- if ($bool ne "&") {
- $part_one = $claws_condition = $part_three = $part_four = "";
- if (!$ignored_list) {
- $ignored_list .= "Ignored $cur_name because it matches an exact age and is an OR match\n";
- }
- next;
- } else {
- $ignored_rules--;
- $exact_age = 1;
- }
- } else {
- $exact = 1;
- $claws_condition = "$claws_condition regexpcase";
- }
- } elsif ($cpart_two eq "ends with") {
- $endswith = 1;
- $claws_condition = "$claws_condition regexpcase";
- } elsif ($cpart_two eq "begins with") {
- $beginswith = 1;
- $claws_condition = "$claws_condition regexpcase";
- } elsif ($cpart_two eq "is in ab") {
- $addrbook = 1;
- $claws_condition = "found_in_addressbook \"$claws_condition\" in \"Any\" ";
- } elsif ($cpart_two eq "isn't in ab") {
- $addrbook = 1;
- $claws_condition = "~found_in_addressbook \"$claws_condition\" in \"Any\" ";
- } elsif ($cpart_two eq "is greater than") {
- if ($size_condition) {
- $claws_condition .= "size_greater";
- }
- if ($age_condition) {
- $claws_condition .= "age_greater";
- }
- } elsif ($cpart_two eq "is less than") {
- if ($size_condition) {
- $claws_condition .= "size_smaller";
- }
- if ($age_condition) {
- $claws_condition .= "age_lower";
- }
- }
-
- if ($exact || $beginswith || $endswith) {
- $cpart_thr = escape_regex($cpart_thr);
- }
- if ($exact) {
- $cpart_thr = "^$cpart_thr\$";
- } elsif ($beginswith) {
- $cpart_thr = "^$cpart_thr";
- } elsif ($endswith) {
- $cpart_thr = "$cpart_thr\$";
- }
- unless ($addrbook) {
- if ($exact_age) {
- my $lower_limit = $cpart_thr-1;
- my $upper_limit = $cpart_thr+1;
- $lower_limit =~ s/^\"//;
- $lower_limit =~ s/\"$//;
- $upper_limit =~ s/^\"//;
- $upper_limit =~ s/\"$//;
- $claws_condition = "$claws_condition"."age_lower"
- . " $upper_limit $bool "
- . "age_greater $lower_limit ";
- } elsif ($size_condition || $age_condition) {
- $claws_condition = "$claws_condition $cpart_thr ";
- } else {
- $claws_condition = "$claws_condition \"$cpart_thr\" ";
- }
- }
- if ($tbird_conditions[1] && $cond_count < $#tbird_conditions) {
- $claws_condition = "$claws_condition$bool ";
- }
- }
- $cond_count++;
- }
- if ($part_one) {
- $conv_rules++;
- push(@claws_filters, "$part_one$claws_condition$part_three$part_four\n");
- }
- }
- }
- }
- push(@claws_filters, "\n");
- return($conv_rules,$ignored_rules,$ignored_list);
- }
- sub rewrite_mailbox_name {
- my ($path) = @_;
- my $new_path;
- my ($front,$back) = split(/\/\//, $path, 2);
- if ($front =~ m/^"mailbox/) {
- $new_path = "\"#mh/$mailbox/";
- } else {
- $new_path = "\"#imap/$mailbox/";
- }
- my ($box,$name) = split(/\//, $back, 2);
- if ($new_path =~ m/^"#mh/) {
- $name =~ s/^Inbox/inbox/;
- $name =~ s/^Sent/sent/;
- $name =~ s/^Drafts/draft/;
- $name =~ s/^Trash/trash/;
- }
- $new_path = $new_path.$name;
- return($new_path);
- }
- sub escape_regex {
- my ($string) = @_;
- my $escstr = "";
- my $symbols = qr/^(?:\[|\]|\{|\}|\(|\)|\||\+|\*|\.|\-|\$|\^)$/;
- my @chars = split(//, $string);
- foreach my $char (@chars) {
- if ($char =~ m/$symbols/) { $char = "\\\\$char"; }
- $escstr .= $char;
- }
- return($escstr);
- }
|