356 lines
11 KiB
Go
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)
|
|
} |