Commit b3f1c3e0ab6c
Changed files (1)
home
common
services
home/common/services/imapfilter-config.lua
@@ -35,39 +35,53 @@ account = IMAP {
-- Load rules from external file
-- Returns a table with rules organized by type
+-- Supports both single-criterion (OR logic) and multi-criterion (AND logic) rules
function load_rules_from_file(filepath)
local rules = {
- from = {}, -- Sender email addresses
- domain = {}, -- Email domains
- subject = {}, -- Subject patterns
- header = {} -- Header field patterns
+ from = {}, -- Sender email addresses (OR logic)
+ domain = {}, -- Email domains (OR logic)
+ subject = {}, -- Subject patterns (OR logic)
+ header = {}, -- Header field patterns (OR logic)
+ body = {}, -- Body patterns (OR logic)
+ multi = {} -- Multi-criteria rules (AND logic within each rule)
}
-
+
local file = io.open(filepath, "r")
if not file then
print("Warning: Could not open rules file: " .. filepath)
return rules
end
-
+
for line in file:lines() do
-- Skip comments and blank lines
line = line:match("^%s*(.-)%s*$") -- Trim whitespace
if line ~= "" and not line:match("^#") then
- -- Parse rule format: TYPE:PATTERN
- local rule_type, pattern = line:match("^(%w+):(.+)$")
- if rule_type and pattern then
+ -- Parse multi-criteria format: TYPE:PATTERN TYPE:PATTERN ...
+ -- Count how many rule types are on this line
+ local criteria = {}
+ for rule_type, pattern in line:gmatch("(%w+):([^%s]+)") do
rule_type = rule_type:lower()
+ table.insert(criteria, {type = rule_type, pattern = pattern})
+ end
+
+ if #criteria == 0 then
+ print("Warning: Invalid rule format: " .. line)
+ elseif #criteria == 1 then
+ -- Single criterion - add to OR set (backward compatible)
+ local rule_type = criteria[1].type
+ local pattern = line:match("^%w+:(.+)$") -- Get full pattern (may contain spaces)
if rules[rule_type] then
table.insert(rules[rule_type], pattern)
else
print("Warning: Unknown rule type '" .. rule_type .. "' in " .. filepath)
end
else
- print("Warning: Invalid rule format (expected TYPE:PATTERN): " .. line)
+ -- Multi-criteria - add to AND set
+ table.insert(rules.multi, criteria)
end
end
end
-
+
file:close()
return rules
end
@@ -76,40 +90,76 @@ end
-- Rule Application Functions
----------
+-- Apply a single criterion to a message set
+function apply_single_criterion(messages, criterion_type, pattern)
+ if criterion_type == 'from' then
+ return messages:contain_from(pattern)
+ elseif criterion_type == 'domain' then
+ return messages:contain_from(pattern)
+ elseif criterion_type == 'subject' then
+ return messages:contain_subject(pattern)
+ elseif criterion_type == 'body' then
+ return messages:contain_body(pattern)
+ elseif criterion_type == 'header' then
+ -- Parse HEADER-NAME:PATTERN
+ local header_name, header_pattern = pattern:match("^([^:]+):(.+)$")
+ if header_name and header_pattern then
+ return messages:contain_header(header_name, header_pattern)
+ else
+ print("Warning: Invalid header pattern: " .. pattern)
+ return Set {}
+ end
+ else
+ print("Warning: Unknown criterion type: " .. criterion_type)
+ return Set {}
+ end
+end
+
-- Apply rules to a message set
function apply_rules(messages, rules, action, folder_name)
local results = Set {}
-
- -- Apply from: rules (sender email)
+
+ -- Apply single-criterion rules (OR logic)
for _, sender in ipairs(rules.from or {}) do
results = results + messages:contain_from(sender)
end
-
- -- Apply domain: rules (entire domain)
+
for _, domain in ipairs(rules.domain or {}) do
- -- Match emails ending with the domain
results = results + messages:contain_from(domain)
end
-
- -- Apply subject: rules (subject patterns)
+
for _, pattern in ipairs(rules.subject or {}) do
results = results + messages:contain_subject(pattern)
end
-
- -- Apply header: rules (custom headers)
+
+ for _, pattern in ipairs(rules.body or {}) do
+ results = results + messages:contain_body(pattern)
+ end
+
for _, header_pattern in ipairs(rules.header or {}) do
- -- Parse HEADER-NAME:PATTERN
local header_name, pattern = header_pattern:match("^([^:]+):(.+)$")
if header_name and pattern then
results = results + messages:contain_header(header_name, pattern)
end
end
-
+
+ -- Apply multi-criteria rules (AND logic within each rule, OR between rules)
+ for _, criteria_list in ipairs(rules.multi or {}) do
+ -- Start with all messages, then intersect with each criterion
+ local multi_results = messages
+ for _, criterion in ipairs(criteria_list) do
+ multi_results = multi_results * apply_single_criterion(messages, criterion.type, criterion.pattern)
+ end
+ results = results + multi_results
+ end
+
-- Perform the action
if #results > 0 then
print(string.format(" → Matched %d message(s)", #results))
if action == 'delete' then
- results:delete_messages()
+ -- Move to Trash instead of permanent deletion
+ account:create_mailbox('Trash')
+ results:move_messages(account['Trash'])
elseif action == 'archive' then
archive_by_year(results)
elseif action == 'move' then