Files
gmail/filters.go
2025-07-28 03:03:24 +03:00

356 lines
11 KiB
Go

package main
import (
"encoding/json"
"fmt"
"log"
"os"
"strings"
)
type GmailFilter struct {
Name string `json:"name"`
Criteria string `json:"criteria"`
Actions string `json:"actions"`
Description string `json:"description"`
}
type FilterSuggestions struct {
Filters []GmailFilter `json:"filters"`
UnsubscribeCandidates []string `json:"unsubscribe_candidates"`
ArchiveCandidates []string `json:"archive_candidates"`
SearchQueries []SearchQuery `json:"search_queries"`
}
type SearchQuery struct {
Purpose string `json:"purpose"`
Query string `json:"query"`
Description string `json:"description"`
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run filters.go <analysis_json_file> [output_format]")
fmt.Println(" analysis_json_file: JSON output from analyze.go")
fmt.Println(" output_format: filters (default), queries, or all")
os.Exit(1)
}
jsonFile := os.Args[1]
outputFormat := "filters"
if len(os.Args) > 2 {
outputFormat = strings.ToLower(os.Args[2])
}
stats, err := loadAnalysisFromJSON(jsonFile)
if err != nil {
log.Fatalf("Error loading analysis JSON: %v", err)
}
suggestions := generateFilterSuggestions(stats)
switch outputFormat {
case "filters":
printFilters(suggestions.Filters)
case "queries":
printSearchQueries(suggestions.SearchQueries)
case "all":
printAllSuggestions(suggestions)
case "json":
outputJSON(suggestions)
default:
fmt.Printf("Unknown output format: %s\n", outputFormat)
os.Exit(1)
}
}
func loadAnalysisFromJSON(filename string) (EmailStats, error) {
var stats EmailStats
file, err := os.Open(filename)
if err != nil {
return stats, err
}
defer file.Close()
decoder := json.NewDecoder(file)
err = decoder.Decode(&stats)
return stats, err
}
func generateFilterSuggestions(stats EmailStats) FilterSuggestions {
suggestions := FilterSuggestions{
Filters: []GmailFilter{},
UnsubscribeCandidates: []string{},
ArchiveCandidates: []string{},
SearchQueries: []SearchQuery{},
}
// Generate filters based on top domains
suggestions.Filters = append(suggestions.Filters, generateDomainFilters(stats.TopDomains)...)
// Generate filters based on subject patterns
suggestions.Filters = append(suggestions.Filters, generatePatternFilters(stats.SubjectPatterns)...)
// Generate filters based on categories
suggestions.Filters = append(suggestions.Filters, generateCategoryFilters(stats.Categories)...)
// Generate unsubscribe candidates
suggestions.UnsubscribeCandidates = generateUnsubscribeCandidates(stats.TopSenders, stats.TopDomains)
// Generate archive candidates
suggestions.ArchiveCandidates = generateArchiveCandidates(stats.TopDomains)
// Generate search queries
suggestions.SearchQueries = generateSearchQueries(stats)
return suggestions
}
func generateDomainFilters(domains []DomainInfo) []GmailFilter {
var filters []GmailFilter
for _, domain := range domains {
if domain.Count < 10 {
continue
}
switch domain.Type {
case "social":
filters = append(filters, GmailFilter{
Name: fmt.Sprintf("Auto-label %s", domain.Domain),
Criteria: fmt.Sprintf("from:@%s", domain.Domain),
Actions: "Apply label: Social, Skip inbox",
Description: fmt.Sprintf("Auto-process social media emails from %s (%d emails)", domain.Domain, domain.Count),
})
case "commerce":
filters = append(filters, GmailFilter{
Name: fmt.Sprintf("Auto-label %s", domain.Domain),
Criteria: fmt.Sprintf("from:@%s", domain.Domain),
Actions: "Apply label: Shopping, Skip inbox",
Description: fmt.Sprintf("Auto-process shopping emails from %s (%d emails)", domain.Domain, domain.Count),
})
case "automated":
filters = append(filters, GmailFilter{
Name: fmt.Sprintf("Auto-archive %s", domain.Domain),
Criteria: fmt.Sprintf("from:@%s", domain.Domain),
Actions: "Apply label: Automated, Skip inbox, Mark as read",
Description: fmt.Sprintf("Auto-archive automated emails from %s (%d emails)", domain.Domain, domain.Count),
})
}
}
return filters
}
func generatePatternFilters(patterns []PatternInfo) []GmailFilter {
var filters []GmailFilter
for _, pattern := range patterns {
if pattern.Count < 5 {
continue
}
switch pattern.Pattern {
case "newsletter":
filters = append(filters, GmailFilter{
Name: "Auto-label newsletters",
Criteria: `subject:(newsletter OR weekly OR monthly OR digest)`,
Actions: "Apply label: Newsletters, Skip inbox",
Description: fmt.Sprintf("Auto-process newsletter emails (%d found)", pattern.Count),
})
case "promotional":
filters = append(filters, GmailFilter{
Name: "Auto-label promotions",
Criteria: `subject:(sale OR deal OR offer OR discount OR "%" OR free)`,
Actions: "Apply label: Promotions, Skip inbox",
Description: fmt.Sprintf("Auto-process promotional emails (%d found)", pattern.Count),
})
case "notification":
filters = append(filters, GmailFilter{
Name: "Auto-label notifications",
Criteria: `subject:(notification OR alert OR reminder)`,
Actions: "Apply label: Notifications",
Description: fmt.Sprintf("Auto-label notification emails (%d found)", pattern.Count),
})
case "transactional":
filters = append(filters, GmailFilter{
Name: "Auto-label receipts",
Criteria: `subject:(receipt OR confirmation OR invoice OR payment)`,
Actions: "Apply label: Receipts, Star it",
Description: fmt.Sprintf("Auto-label important transactional emails (%d found)", pattern.Count),
})
}
}
return filters
}
func generateCategoryFilters(categories map[string]int) []GmailFilter {
var filters []GmailFilter
for category, count := range categories {
if count < 20 {
continue
}
switch category {
case "newsletters":
filters = append(filters, GmailFilter{
Name: "Auto-handle newsletters",
Criteria: `from:(noreply OR no-reply) OR subject:unsubscribe`,
Actions: "Apply label: Newsletters, Skip inbox",
Description: fmt.Sprintf("Auto-process newsletter-type emails (%d found)", count),
})
case "work":
filters = append(filters, GmailFilter{
Name: "Auto-label work tools",
Criteria: `from:(slack.com OR github.com OR jira OR atlassian)`,
Actions: "Apply label: Work",
Description: fmt.Sprintf("Auto-label work-related tool emails (%d found)", count),
})
case "finance":
filters = append(filters, GmailFilter{
Name: "Auto-label finance",
Criteria: `subject:(payment OR invoice OR receipt) OR from:(paypal OR stripe OR bank)`,
Actions: "Apply label: Finance, Star it",
Description: fmt.Sprintf("Auto-label financial emails (%d found)", count),
})
}
}
return filters
}
func generateUnsubscribeCandidates(senders []SenderInfo, domains []DomainInfo) []string {
var candidates []string
// High-volume newsletter senders
for _, sender := range senders {
if sender.Count > 20 && (strings.Contains(strings.ToLower(sender.Email), "noreply") ||
strings.Contains(strings.ToLower(sender.Email), "no-reply") ||
strings.Contains(strings.ToLower(sender.Email), "newsletter")) {
candidates = append(candidates, fmt.Sprintf("%s (%d emails)", sender.Email, sender.Count))
}
}
// High-volume automated domains
for _, domain := range domains {
if domain.Count > 30 && domain.Type == "automated" {
candidates = append(candidates, fmt.Sprintf("All emails from @%s (%d emails)", domain.Domain, domain.Count))
}
}
return candidates
}
func generateArchiveCandidates(domains []DomainInfo) []string {
var candidates []string
for _, domain := range domains {
if domain.Count > 50 && (domain.Type == "automated" || domain.Type == "social") {
candidates = append(candidates, fmt.Sprintf("@%s (%d emails, %s)", domain.Domain, domain.Count, domain.Type))
}
}
return candidates
}
func generateSearchQueries(stats EmailStats) []SearchQuery {
queries := []SearchQuery{
{
Purpose: "Find old newsletters",
Query: `subject:(newsletter OR weekly OR monthly) older_than:6m`,
Description: "Find newsletters older than 6 months that can be safely deleted",
},
{
Purpose: "Find promotional emails",
Query: `subject:(sale OR deal OR offer OR discount) older_than:3m`,
Description: "Find promotional emails older than 3 months",
},
{
Purpose: "Find social notifications",
Query: `from:(facebook OR twitter OR linkedin OR instagram) older_than:1m`,
Description: "Find social media notifications older than 1 month",
},
{
Purpose: "Find automated emails",
Query: `from:(noreply OR no-reply) older_than:1y`,
Description: "Find automated emails older than 1 year",
},
{
Purpose: "Find large attachments",
Query: `has:attachment larger:10M`,
Description: "Find emails with attachments larger than 10MB",
},
{
Purpose: "Find unread old emails",
Query: `is:unread older_than:1m`,
Description: "Find unread emails older than 1 month that might need attention",
},
}
// Add domain-specific queries for top domains
for i, domain := range stats.TopDomains {
if i >= 3 || domain.Count < 50 {
break
}
queries = append(queries, SearchQuery{
Purpose: fmt.Sprintf("Review %s emails", domain.Domain),
Query: fmt.Sprintf(`from:@%s`, domain.Domain),
Description: fmt.Sprintf("Review all %d emails from %s", domain.Count, domain.Domain),
})
}
return queries
}
func printFilters(filters []GmailFilter) {
fmt.Printf("\n=== GMAIL FILTER SUGGESTIONS ===\n\n")
fmt.Printf("To create these filters in Gmail:\n")
fmt.Printf("1. Go to Gmail Settings > Filters and Blocked Addresses\n")
fmt.Printf("2. Click 'Create a new filter'\n")
fmt.Printf("3. Use the criteria below, then apply the suggested actions\n\n")
for i, filter := range filters {
fmt.Printf("%d. %s\n", i+1, filter.Name)
fmt.Printf(" Criteria: %s\n", filter.Criteria)
fmt.Printf(" Actions: %s\n", filter.Actions)
fmt.Printf(" Description: %s\n\n", filter.Description)
}
}
func printSearchQueries(queries []SearchQuery) {
fmt.Printf("\n=== GMAIL SEARCH QUERIES ===\n\n")
fmt.Printf("Copy and paste these queries into Gmail's search box:\n\n")
for i, query := range queries {
fmt.Printf("%d. %s\n", i+1, query.Purpose)
fmt.Printf(" Query: %s\n", query.Query)
fmt.Printf(" Description: %s\n\n", query.Description)
}
}
func printAllSuggestions(suggestions FilterSuggestions) {
printFilters(suggestions.Filters)
fmt.Printf("\n=== UNSUBSCRIBE CANDIDATES ===\n")
fmt.Printf("Consider unsubscribing from these high-volume senders:\n\n")
for i, candidate := range suggestions.UnsubscribeCandidates {
fmt.Printf("%d. %s\n", i+1, candidate)
}
fmt.Printf("\n=== BULK ARCHIVE CANDIDATES ===\n")
fmt.Printf("Consider bulk archiving emails from these domains:\n\n")
for i, candidate := range suggestions.ArchiveCandidates {
fmt.Printf("%d. %s\n", i+1, candidate)
}
printSearchQueries(suggestions.SearchQueries)
}
func outputJSON(suggestions FilterSuggestions) {
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
encoder.Encode(suggestions)
}