-
Notifications
You must be signed in to change notification settings - Fork 0
/
filter_converter.py
144 lines (118 loc) · 5.24 KB
/
filter_converter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import re
import sys
from typing import List, Dict, Tuple
def read_thunderbird_filters(file_path: str) -> str:
"""Read the contents of a Thunderbird filter file."""
try:
with open(file_path, 'r', encoding='utf-8') as file:
return file.read()
except FileNotFoundError:
raise FileNotFoundError(f"Error: File '{file_path}' not found.")
except IOError as e:
raise IOError(f"Error reading file '{file_path}': {e}")
def parse_thunderbird_filters(thunderbird_filters: str) -> List[Dict[str, str]]:
"""Parse Thunderbird filters into a list of dictionaries."""
filters = []
current_filter = {}
for line in thunderbird_filters.split('\n'):
line = line.strip()
if not line:
continue
if line.startswith('name='):
if current_filter and 'name' in current_filter and 'condition' in current_filter:
filters.append(current_filter)
current_filter = {}
if '=' in line:
key, value = line.split('=', 1)
value = value.strip('"')
if key == 'action':
if 'actions' not in current_filter:
current_filter['actions'] = []
current_filter['actions'].append(value)
elif key == 'name':
current_filter['name'] = value
else:
current_filter[key] = value
# Check that the current filter has a name and condition before appending
if current_filter and 'name' in current_filter and 'condition' in current_filter:
filters.append(current_filter)
return filters
def clean_header(header: str) -> str:
"""Remove unnecessary escaping from the header."""
# Only unescape inner quotes if they are double-escaped
return header.replace('\\"', '"').strip('"')
def convert_condition(condition: str) -> Tuple[str, List[str]]:
"""Convert a Thunderbird condition to Sieve format."""
condition = condition.strip()
if condition.startswith('OR '):
operator = 'anyof'
condition = condition[3:]
elif condition.startswith('AND '):
operator = 'allof'
condition = condition[4:]
else:
operator = 'anyof'
# Regex to match the parts, including escaped quotes
parts = re.findall(r'\(([^,]+),\s*([^,]+),\s*(.+?)\)', condition)
sieve_conditions = []
for header, operation, value in parts:
# Clean header and value
header = clean_header(header)
value = clean_header(value)
if header == "to or cc":
sieve_conditions.append(f'header :contains "to" "{value}"')
sieve_conditions.append(f'header :contains "cc" "{value}"')
elif operation == 'contains':
sieve_conditions.append(f'header :contains "{header}" "{value}"')
elif operation == "doesn't contain":
sieve_conditions.append(f'not header :contains "{header}" "{value}"')
return operator, sieve_conditions
def convert_to_sieve(thunderbird_filter: Dict[str, str]) -> str:
"""Convert a single Thunderbird filter to a Sieve rule."""
name = thunderbird_filter.get('name', 'Unnamed Filter')
condition = thunderbird_filter.get('condition', '')
actions = []
if 'actionValue' in thunderbird_filter:
full_path = thunderbird_filter['actionValue']
folder_match = re.search(r'INBOX/(.+)$', full_path)
if folder_match:
folder = folder_match.group(1)
actions.append(f'\tfileinto "INBOX/{folder}";')
if 'actions' in thunderbird_filter:
if "Mark read" in thunderbird_filter['actions']:
actions.append('\tsetflag "\\\\Seen";')
if "Stop execution" in thunderbird_filter['actions']:
actions.append('\tstop;')
operator, sieve_conditions = convert_condition(condition)
sieve_rule = f"# rule:[{name}]\n"
sieve_rule += f"if {operator} (\n "
sieve_rule += ",\n ".join(sieve_conditions)
sieve_rule += "\n)\n{\n" + "\n".join(actions) + "\n}"
return sieve_rule
def thunderbird_to_sieve(thunderbird_filters: str) -> str:
"""Convert Thunderbird filters to Sieve rules."""
filters = parse_thunderbird_filters(thunderbird_filters)
sieve_rules = []
for filter in filters:
rule = convert_to_sieve(filter)
sieve_rules.append(rule)
return 'require ["fileinto", "imap4flags"];\n\n' + "\n\n".join(sieve_rules)
def main():
"""Main function to handle command-line arguments and file operations."""
if len(sys.argv) < 2 or len(sys.argv) > 3:
print("Usage: python script.py path/to/msgFilterRules.dat [output_file.sieve]")
sys.exit(1)
input_file_path = sys.argv[1]
output_file_path = sys.argv[2] if len(sys.argv) == 3 else "roundcube.sieve"
try:
thunderbird_filters = read_thunderbird_filters(input_file_path)
sieve_rules = thunderbird_to_sieve(thunderbird_filters)
with open(output_file_path, 'w', encoding='utf-8') as f:
f.write(sieve_rules)
print(f"Sieve rules have been successfully written to {output_file_path}")
print(f"Total rules converted: {sieve_rules.count('# rule:')}")
except Exception as e:
print(f"An error occurred: {e}")
sys.exit(1)
if __name__ == "__main__":
main()