mirror of
https://github.com/ghostersk/gowebmail.git
synced 2026-09-13 23:30:37 +01:00
108 lines
2.5 KiB
Go
108 lines
2.5 KiB
Go
// Package rules implements mail-filter matching: given a message and an account's
|
|
// active rules (already ordered by priority), find the first rule that matches.
|
|
package rules
|
|
|
|
import "strings"
|
|
|
|
// Condition is one field/op/value test. Mirrors models.RuleCondition but this package
|
|
// stays free of the models/db dependency so Match is trivially unit-testable.
|
|
type Condition struct {
|
|
Field string
|
|
Op string
|
|
Value string
|
|
}
|
|
|
|
// MessageFields is the subset of a message's data rules can match against.
|
|
type MessageFields struct {
|
|
From string
|
|
To string
|
|
Subject string
|
|
Body string
|
|
HasAttachment bool
|
|
RecipientType string // "to" | "cc" | "bcc"
|
|
}
|
|
|
|
// Rule is one filter: conditions (AND'd or OR'd per MatchType) plus an action.
|
|
type Rule struct {
|
|
ID int64
|
|
Priority int
|
|
Conditions []Condition
|
|
MatchType string // "all" (AND, default) | "any" (OR)
|
|
Action string
|
|
ActionValue string
|
|
ActionOptions map[string]any
|
|
}
|
|
|
|
// Match returns the first rule (by priority, ascending) whose conditions match msg,
|
|
// or nil if none match. Callers must pass rules pre-filtered to is_active and pre-sorted
|
|
// by priority ascending (ListActiveRules already does this).
|
|
func Match(msg MessageFields, activeRules []Rule) *Rule {
|
|
for i := range activeRules {
|
|
if ruleMatches(&activeRules[i], msg) {
|
|
return &activeRules[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ruleMatches(r *Rule, msg MessageFields) bool {
|
|
if len(r.Conditions) == 0 {
|
|
return false
|
|
}
|
|
if r.MatchType == "any" {
|
|
for _, c := range r.Conditions {
|
|
if conditionMatches(c, msg) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
// default "all" (AND)
|
|
for _, c := range r.Conditions {
|
|
if !conditionMatches(c, msg) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func conditionMatches(c Condition, msg MessageFields) bool {
|
|
var target string
|
|
switch c.Field {
|
|
case "from":
|
|
target = msg.From
|
|
case "to":
|
|
target = msg.To
|
|
case "subject":
|
|
target = msg.Subject
|
|
case "body":
|
|
target = msg.Body
|
|
case "has_attachment":
|
|
if msg.HasAttachment {
|
|
target = "yes"
|
|
} else {
|
|
target = "no"
|
|
}
|
|
case "recipient_type":
|
|
target = msg.RecipientType
|
|
default:
|
|
return false
|
|
}
|
|
return matchOp(c.Op, c.Value, target)
|
|
}
|
|
|
|
func matchOp(op, value, target string) bool {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
target = strings.ToLower(target)
|
|
switch op {
|
|
case "contains":
|
|
return value != "" && strings.Contains(target, value)
|
|
case "equals":
|
|
return target == value
|
|
case "starts_with":
|
|
return value != "" && strings.HasPrefix(target, value)
|
|
default:
|
|
return false
|
|
}
|
|
}
|