Skip to content

Commit f4aebfb

Browse files
committed
完善过滤规则历史操作和站内弹窗
1 parent 3ebc969 commit f4aebfb

11 files changed

Lines changed: 447 additions & 100 deletions

File tree

api.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ func setupAPI(r *gin.Engine) {
295295

296296
r.DELETE("/api/filters/:id", func(c *gin.Context) {
297297
id := c.Param("id")
298+
if id == DefaultSenderBlacklistID || id == DefaultSenderWhitelistID {
299+
c.JSON(http.StatusBadRequest, gin.H{"error": "默认发件人过滤规则不能删除"})
300+
return
301+
}
298302
configLock.Lock()
299303
newFilters := make([]FilterRule, 0, len(config.FilterRules))
300304
for _, f := range config.FilterRules {
@@ -311,6 +315,26 @@ func setupAPI(r *gin.Engine) {
311315
c.JSON(http.StatusOK, gin.H{"success": true})
312316
})
313317

318+
r.POST("/api/filters/default-senders", func(c *gin.Context) {
319+
var req struct {
320+
Mode string `json:"mode"`
321+
Sender string `json:"sender"`
322+
}
323+
if err := c.ShouldBindJSON(&req); err != nil {
324+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
325+
return
326+
}
327+
if req.Mode != "blacklist" && req.Mode != "whitelist" {
328+
c.JSON(http.StatusBadRequest, gin.H{"error": "mode must be blacklist or whitelist"})
329+
return
330+
}
331+
configLock.Lock()
332+
rule, added := addSenderToDefaultFilterRuleNoLock(req.Mode, req.Sender)
333+
saveConfigNoLock()
334+
configLock.Unlock()
335+
c.JSON(http.StatusOK, gin.H{"success": true, "added": added, "rule": rule})
336+
})
337+
314338
// ===== 规则管理 =====
315339
r.GET("/api/rules", func(c *gin.Context) {
316340
configLock.RLock()

config.example.json

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,25 @@
2828
"name": "转发全部",
2929
"source_account": "all",
3030
"target_webhook": "wh_17700000000",
31-
"filters": [],
3231
"filter_rule_ids": [],
3332
"enabled": true
3433
}
3534
],
3635
"filter_rules": [
3736
{
38-
"id": "filter_17700000000",
39-
"name": "屏蔽营销邮件",
40-
"type": "content",
37+
"id": "default_sender_blacklist",
38+
"name": "默认发件人黑名单",
39+
"type": "sender",
4140
"mode": "blacklist",
42-
"patterns": [
43-
"unsubscribe",
44-
"退订"
45-
],
41+
"patterns": [],
42+
"enabled": true
43+
},
44+
{
45+
"id": "default_sender_whitelist",
46+
"name": "默认发件人白名单",
47+
"type": "sender",
48+
"mode": "whitelist",
49+
"patterns": [],
4650
"enabled": true
4751
}
4852
],

config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ func loadConfig() {
2121
if err == nil {
2222
json.Unmarshal(data, &config)
2323
} else {
24+
ensureDefaultSenderFilterRulesNoLock()
2425
saveConfigNoLock()
26+
return
2527
}
28+
ensureDefaultSenderFilterRulesNoLock()
29+
saveConfigNoLock()
2630
}
2731

2832
func saveConfigNoLock() {

filter.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import (
55
"strings"
66
)
77

8+
const (
9+
DefaultSenderBlacklistID = "default_sender_blacklist"
10+
DefaultSenderWhitelistID = "default_sender_whitelist"
11+
)
12+
813
// FilterRule 是独立的邮件过滤规则,可被多个转发规则复用。
914
type FilterRule struct {
1015
ID string `json:"id"`
@@ -48,6 +53,69 @@ func normalizeFilterRule(rule FilterRule) FilterRule {
4853
return rule
4954
}
5055

56+
func ensureDefaultSenderFilterRulesNoLock() {
57+
ensureDefaultFilterRuleNoLock(FilterRule{
58+
ID: DefaultSenderBlacklistID,
59+
Name: "默认发件人黑名单",
60+
Type: "sender",
61+
Mode: "blacklist",
62+
Patterns: []string{},
63+
Enabled: true,
64+
})
65+
ensureDefaultFilterRuleNoLock(FilterRule{
66+
ID: DefaultSenderWhitelistID,
67+
Name: "默认发件人白名单",
68+
Type: "sender",
69+
Mode: "whitelist",
70+
Patterns: []string{},
71+
Enabled: true,
72+
})
73+
}
74+
75+
func ensureDefaultFilterRuleNoLock(rule FilterRule) {
76+
rule = normalizeFilterRule(rule)
77+
for i, existing := range config.FilterRules {
78+
if existing.ID == rule.ID {
79+
existing.Name = rule.Name
80+
existing.Type = rule.Type
81+
existing.Mode = rule.Mode
82+
config.FilterRules[i] = normalizeFilterRule(existing)
83+
return
84+
}
85+
}
86+
config.FilterRules = append(config.FilterRules, rule)
87+
}
88+
89+
func addSenderToDefaultFilterRuleNoLock(mode string, sender string) (FilterRule, bool) {
90+
sender = strings.TrimSpace(sender)
91+
if sender == "" {
92+
return FilterRule{}, false
93+
}
94+
95+
ruleID := DefaultSenderBlacklistID
96+
if mode == "whitelist" {
97+
ruleID = DefaultSenderWhitelistID
98+
}
99+
ensureDefaultSenderFilterRulesNoLock()
100+
101+
for i, rule := range config.FilterRules {
102+
if rule.ID != ruleID {
103+
continue
104+
}
105+
rule = normalizeFilterRule(rule)
106+
for _, pattern := range rule.Patterns {
107+
if strings.EqualFold(pattern, sender) {
108+
config.FilterRules[i] = rule
109+
return rule, false
110+
}
111+
}
112+
rule.Patterns = append(rule.Patterns, sender)
113+
config.FilterRules[i] = normalizeFilterRule(rule)
114+
return config.FilterRules[i], true
115+
}
116+
return FilterRule{}, false
117+
}
118+
51119
func applyFilterRules(ids []string, rules map[string]FilterRule, msg *Message) filterResult {
52120
for _, id := range ids {
53121
rule, ok := rules[id]

main_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,34 @@ func TestApplyFilterRules_WhitelistRequiresContentMatch(t *testing.T) {
118118
t.Fatalf("expected whitelist content rule to allow matched message: %s", got.Reason)
119119
}
120120
}
121+
122+
func TestAddSenderToDefaultFilterRuleNoLock_CreatesDefaultsAndDeduplicates(t *testing.T) {
123+
oldConfig := config
124+
t.Cleanup(func() {
125+
config = oldConfig
126+
})
127+
config = Config{}
128+
129+
rule, added := addSenderToDefaultFilterRuleNoLock("blacklist", "spam@example.com")
130+
if !added {
131+
t.Fatalf("expected sender to be added")
132+
}
133+
if rule.ID != DefaultSenderBlacklistID {
134+
t.Fatalf("expected blacklist rule, got %q", rule.ID)
135+
}
136+
if len(rule.Patterns) != 1 || rule.Patterns[0] != "spam@example.com" {
137+
t.Fatalf("expected sender in blacklist patterns, got %#v", rule.Patterns)
138+
}
139+
140+
rule, added = addSenderToDefaultFilterRuleNoLock("blacklist", "SPAM@example.com")
141+
if added {
142+
t.Fatalf("expected duplicate sender to be ignored")
143+
}
144+
if len(rule.Patterns) != 1 {
145+
t.Fatalf("expected duplicate sender to keep one pattern, got %#v", rule.Patterns)
146+
}
147+
148+
if len(config.FilterRules) != 2 {
149+
t.Fatalf("expected both default sender rules to exist, got %d", len(config.FilterRules))
150+
}
151+
}

models.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ type ForwardRule struct {
3232
Name string `json:"name"`
3333
SourceAccount string `json:"source_account"` // 邮箱账号ID,"all" 表示所有
3434
TargetWebhook string `json:"target_webhook"` // Webhook目标ID
35-
Filters []string `json:"filters"` // 主题关键词过滤
3635
FilterRuleIDs []string `json:"filter_rule_ids"` // 独立过滤规则ID
3736
Enabled bool `json:"enabled"`
3837
}

processor.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"fmt"
5-
"strings"
65
"time"
76
)
87

@@ -61,20 +60,6 @@ func processPendingMessages() {
6160
continue
6261
}
6362

64-
// 检查关键词过滤
65-
if len(rule.Filters) > 0 {
66-
matched := false
67-
for _, f := range rule.Filters {
68-
if strings.Contains(strings.ToLower(msg.Subject), strings.ToLower(f)) {
69-
matched = true
70-
break
71-
}
72-
}
73-
if !matched {
74-
continue
75-
}
76-
}
77-
7863
filterResult := applyFilterRules(rule.FilterRuleIDs, filterRules, &msg)
7964
if !filterResult.Allowed {
8065
filterBlocked = true

0 commit comments

Comments
 (0)