Phase 6: rules editor + unsubscribe detection (AUD-72)
- FR-11 rules editor: plain-text area at /rules, saved and versioned (SaveRules appends a version; restore re-saves an old version), parse errors surfaced, one-click re-classify. The reply model now reads the rules document too (FR-11). - FR-7 unsubscribe: parse and store List-Unsubscribe / List-Unsubscribe-Post (migration v6), detect email and web links (internal/unsubscribe), a domain-level ignore list, and an explicit record of what was marked. No code path contacts an unsubscribe link; acting on one needs Sam's approval. - Migration v6 is additive; unsubscribe gains url/updated_at. - Tests: detector, parser, store migration/records/candidates, rules editor, unsubscribe handlers, message-detail rendering. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -27,8 +27,13 @@ type ParsedMessage struct {
|
||||
Date time.Time
|
||||
// MessageID is the RFC822 Message-ID header, used for reply threading
|
||||
// (Phase 5, FR-8).
|
||||
MessageID string
|
||||
SourceAddress string
|
||||
MessageID string
|
||||
// ListUnsubscribe and ListUnsubscribePost are the RFC 2369/8058 headers that
|
||||
// name the unsubscribe targets (Phase 6, FR-7). They are stored raw so the
|
||||
// detector can run later without a refetch.
|
||||
ListUnsubscribe string
|
||||
ListUnsubscribePost string
|
||||
SourceAddress string
|
||||
// SourceHeader records which header the source address came from, so FR-2
|
||||
// decisions are auditable.
|
||||
SourceHeader string
|
||||
@@ -43,11 +48,13 @@ func ParseMessage(raw []byte) (*ParsedMessage, error) {
|
||||
}
|
||||
h := msg.Header
|
||||
pm := &ParsedMessage{
|
||||
From: decodeHeader(h.Get("From")),
|
||||
To: decodeHeader(h.Get("To")),
|
||||
Subject: decodeHeader(h.Get("Subject")),
|
||||
Date: parseDate(h.Get("Date")),
|
||||
MessageID: strings.TrimSpace(h.Get("Message-Id")),
|
||||
From: decodeHeader(h.Get("From")),
|
||||
To: decodeHeader(h.Get("To")),
|
||||
Subject: decodeHeader(h.Get("Subject")),
|
||||
Date: parseDate(h.Get("Date")),
|
||||
MessageID: strings.TrimSpace(h.Get("Message-Id")),
|
||||
ListUnsubscribe: strings.TrimSpace(h.Get("List-Unsubscribe")),
|
||||
ListUnsubscribePost: strings.TrimSpace(h.Get("List-Unsubscribe-Post")),
|
||||
}
|
||||
pm.SourceAddress, pm.SourceHeader = SourceAddress(textproto.MIMEHeader(h))
|
||||
pm.BodyText = extractBody(textproto.MIMEHeader(h), msg.Body)
|
||||
|
||||
Reference in New Issue
Block a user