500 lines
16 KiB
Go
500 lines
16 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"sort"
|
||
"strings"
|
||
)
|
||
|
||
type CleanupRecommendations struct {
|
||
SafeToDelete []DeleteRecommendation `json:"safe_to_delete"`
|
||
BulkArchive []ArchiveRecommendation `json:"bulk_archive"`
|
||
UnsubscribeTargets []UnsubscribeTarget `json:"unsubscribe_targets"`
|
||
AttentionNeeded []AttentionItem `json:"attention_needed"`
|
||
StorageStats StorageStats `json:"storage_stats"`
|
||
}
|
||
|
||
type DeleteRecommendation struct {
|
||
Category string `json:"category"`
|
||
Query string `json:"query"`
|
||
Count int `json:"estimated_count"`
|
||
Description string `json:"description"`
|
||
Risk string `json:"risk"`
|
||
Examples []string `json:"examples"`
|
||
}
|
||
|
||
type ArchiveRecommendation struct {
|
||
Category string `json:"category"`
|
||
Query string `json:"query"`
|
||
Count int `json:"estimated_count"`
|
||
Description string `json:"description"`
|
||
Reason string `json:"reason"`
|
||
}
|
||
|
||
type UnsubscribeTarget struct {
|
||
Sender string `json:"sender"`
|
||
Count int `json:"count"`
|
||
LastEmail string `json:"last_email"`
|
||
Category string `json:"category"`
|
||
Priority string `json:"priority"`
|
||
Reason string `json:"reason"`
|
||
}
|
||
|
||
type AttentionItem struct {
|
||
Category string `json:"category"`
|
||
Query string `json:"query"`
|
||
Count int `json:"count"`
|
||
Description string `json:"description"`
|
||
Action string `json:"recommended_action"`
|
||
}
|
||
|
||
type StorageStats struct {
|
||
TotalEmails int `json:"total_emails"`
|
||
DeletionPotential int `json:"deletion_potential"`
|
||
ArchivePotential int `json:"archive_potential"`
|
||
PercentageReduction float64 `json:"percentage_reduction"`
|
||
CategoryBreakdown map[string]int `json:"category_breakdown"`
|
||
}
|
||
|
||
func main() {
|
||
if len(os.Args) < 2 {
|
||
fmt.Println("Usage: go run cleanup.go <analysis_json_file> [output_format]")
|
||
fmt.Println(" analysis_json_file: JSON output from analyze.go")
|
||
fmt.Println(" output_format: summary (default), detailed, or json")
|
||
os.Exit(1)
|
||
}
|
||
|
||
jsonFile := os.Args[1]
|
||
outputFormat := "summary"
|
||
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)
|
||
}
|
||
|
||
recommendations := generateCleanupRecommendations(stats)
|
||
|
||
switch outputFormat {
|
||
case "summary":
|
||
printSummary(recommendations)
|
||
case "detailed":
|
||
printDetailed(recommendations)
|
||
case "json":
|
||
outputJSON(recommendations)
|
||
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 generateCleanupRecommendations(stats EmailStats) CleanupRecommendations {
|
||
recommendations := CleanupRecommendations{
|
||
SafeToDelete: []DeleteRecommendation{},
|
||
BulkArchive: []ArchiveRecommendation{},
|
||
UnsubscribeTargets: []UnsubscribeTarget{},
|
||
AttentionNeeded: []AttentionItem{},
|
||
StorageStats: StorageStats{
|
||
TotalEmails: stats.TotalEmails,
|
||
CategoryBreakdown: stats.Categories,
|
||
},
|
||
}
|
||
|
||
// Generate safe delete recommendations
|
||
recommendations.SafeToDelete = generateDeleteRecommendations(stats)
|
||
|
||
// Generate bulk archive recommendations
|
||
recommendations.BulkArchive = generateArchiveRecommendations(stats)
|
||
|
||
// Generate unsubscribe targets
|
||
recommendations.UnsubscribeTargets = generateUnsubscribeTargets(stats)
|
||
|
||
// Generate attention needed items
|
||
recommendations.AttentionNeeded = generateAttentionItems(stats)
|
||
|
||
// Calculate storage impact
|
||
recommendations.StorageStats = calculateStorageImpact(stats, recommendations)
|
||
|
||
return recommendations
|
||
}
|
||
|
||
func generateDeleteRecommendations(stats EmailStats) []DeleteRecommendation {
|
||
var recommendations []DeleteRecommendation
|
||
|
||
// Old promotional emails
|
||
if count, ok := stats.Categories["newsletters"]; ok && count > 50 {
|
||
recommendations = append(recommendations, DeleteRecommendation{
|
||
Category: "Old Newsletters",
|
||
Query: `subject:(newsletter OR weekly OR monthly) older_than:6m`,
|
||
Count: count / 2, // Estimate half are old
|
||
Description: "Newsletter emails older than 6 months",
|
||
Risk: "Low",
|
||
Examples: []string{"Weekly digest emails", "Monthly newsletters", "Company updates"},
|
||
})
|
||
}
|
||
|
||
// Old social media notifications
|
||
if socialCount := countSocialEmails(stats.TopDomains); socialCount > 30 {
|
||
recommendations = append(recommendations, DeleteRecommendation{
|
||
Category: "Social Media Notifications",
|
||
Query: `from:(facebook OR twitter OR linkedin OR instagram) older_than:2m`,
|
||
Count: socialCount * 2 / 3, // Estimate 2/3 are old
|
||
Description: "Social media notifications older than 2 months",
|
||
Risk: "Low",
|
||
Examples: []string{"Facebook notifications", "Twitter alerts", "LinkedIn updates"},
|
||
})
|
||
}
|
||
|
||
// Old promotional emails
|
||
promotionalCount := 0
|
||
for _, pattern := range stats.SubjectPatterns {
|
||
if pattern.Pattern == "promotional" {
|
||
promotionalCount = pattern.Count
|
||
break
|
||
}
|
||
}
|
||
if promotionalCount > 20 {
|
||
recommendations = append(recommendations, DeleteRecommendation{
|
||
Category: "Old Promotions",
|
||
Query: `subject:(sale OR deal OR offer OR discount) older_than:3m`,
|
||
Count: promotionalCount * 3 / 4, // Estimate 3/4 are old
|
||
Description: "Promotional/sales emails older than 3 months",
|
||
Risk: "Low",
|
||
Examples: []string{"Sales announcements", "Discount offers", "Flash sales"},
|
||
})
|
||
}
|
||
|
||
// Automated system emails
|
||
automatedCount := countAutomatedEmails(stats.TopDomains)
|
||
if automatedCount > 40 {
|
||
recommendations = append(recommendations, DeleteRecommendation{
|
||
Category: "Old System Notifications",
|
||
Query: `from:(noreply OR no-reply) subject:(notification OR alert) older_than:1y`,
|
||
Count: automatedCount / 2,
|
||
Description: "System notifications and alerts older than 1 year",
|
||
Risk: "Medium",
|
||
Examples: []string{"System alerts", "Automated notifications", "Service updates"},
|
||
})
|
||
}
|
||
|
||
return recommendations
|
||
}
|
||
|
||
func generateArchiveRecommendations(stats EmailStats) []ArchiveRecommendation {
|
||
var recommendations []ArchiveRecommendation
|
||
|
||
// Archive newsletters
|
||
if count, ok := stats.Categories["newsletters"]; ok && count > 30 {
|
||
recommendations = append(recommendations, ArchiveRecommendation{
|
||
Category: "All Newsletters",
|
||
Query: `from:(noreply OR no-reply) OR subject:(newsletter OR unsubscribe)`,
|
||
Count: count,
|
||
Description: "Move all newsletter-type emails out of inbox",
|
||
Reason: "Newsletters rarely require immediate action",
|
||
})
|
||
}
|
||
|
||
// Archive social media
|
||
if count, ok := stats.Categories["social"]; ok && count > 20 {
|
||
recommendations = append(recommendations, ArchiveRecommendation{
|
||
Category: "Social Media",
|
||
Query: `from:(facebook OR twitter OR linkedin OR instagram OR youtube)`,
|
||
Count: count,
|
||
Description: "Move social media notifications out of inbox",
|
||
Reason: "Social notifications can be checked on the platforms directly",
|
||
})
|
||
}
|
||
|
||
// Archive automated emails
|
||
automatedCount := countAutomatedEmails(stats.TopDomains)
|
||
if automatedCount > 25 {
|
||
recommendations = append(recommendations, ArchiveRecommendation{
|
||
Category: "Automated Emails",
|
||
Query: `from:(noreply OR no-reply OR automated)`,
|
||
Count: automatedCount,
|
||
Description: "Move automated system emails out of inbox",
|
||
Reason: "Automated emails are usually informational only",
|
||
})
|
||
}
|
||
|
||
return recommendations
|
||
}
|
||
|
||
func generateUnsubscribeTargets(stats EmailStats) []UnsubscribeTarget {
|
||
var targets []UnsubscribeTarget
|
||
|
||
// High-volume newsletter senders
|
||
for _, sender := range stats.TopSenders {
|
||
if sender.Count <= 10 {
|
||
break
|
||
}
|
||
|
||
priority := "medium"
|
||
reason := ""
|
||
category := ""
|
||
|
||
email := strings.ToLower(sender.Email)
|
||
|
||
if strings.Contains(email, "noreply") || strings.Contains(email, "no-reply") {
|
||
category = "automated"
|
||
if sender.Count > 50 {
|
||
priority = "high"
|
||
reason = "Very high volume automated sender"
|
||
} else if sender.Count > 25 {
|
||
priority = "medium"
|
||
reason = "High volume automated sender"
|
||
} else {
|
||
priority = "low"
|
||
reason = "Moderate volume automated sender"
|
||
}
|
||
} else if strings.Contains(email, "newsletter") || strings.Contains(email, "marketing") {
|
||
category = "newsletter"
|
||
priority = "medium"
|
||
reason = "Newsletter or marketing emails"
|
||
} else if containsSocialDomain(sender.Domain) {
|
||
category = "social"
|
||
priority = "low"
|
||
reason = "Social media notifications"
|
||
} else {
|
||
category = "other"
|
||
priority = "low"
|
||
reason = "High volume sender - review manually"
|
||
}
|
||
|
||
if sender.Count > 15 {
|
||
targets = append(targets, UnsubscribeTarget{
|
||
Sender: sender.Email,
|
||
Count: sender.Count,
|
||
Category: category,
|
||
Priority: priority,
|
||
Reason: reason,
|
||
})
|
||
}
|
||
}
|
||
|
||
// Sort by priority and count
|
||
sort.Slice(targets, func(i, j int) bool {
|
||
if targets[i].Priority != targets[j].Priority {
|
||
priorityOrder := map[string]int{"high": 3, "medium": 2, "low": 1}
|
||
return priorityOrder[targets[i].Priority] > priorityOrder[targets[j].Priority]
|
||
}
|
||
return targets[i].Count > targets[j].Count
|
||
})
|
||
|
||
return targets
|
||
}
|
||
|
||
func generateAttentionItems(stats EmailStats) []AttentionItem {
|
||
var items []AttentionItem
|
||
|
||
// High volume personal senders
|
||
for _, sender := range stats.TopSenders {
|
||
if sender.Count > 50 && !strings.Contains(strings.ToLower(sender.Email), "noreply") &&
|
||
!containsAutomatedKeywords(sender.Email) {
|
||
items = append(items, AttentionItem{
|
||
Category: "High Volume Personal",
|
||
Query: fmt.Sprintf(`from:%s`, sender.Email),
|
||
Count: sender.Count,
|
||
Description: fmt.Sprintf("Very high email volume from %s", sender.Email),
|
||
Action: "Review relationship or create filters",
|
||
})
|
||
}
|
||
}
|
||
|
||
// Old unread emails
|
||
items = append(items, AttentionItem{
|
||
Category: "Old Unread",
|
||
Query: `is:unread older_than:1m`,
|
||
Count: 0, // Would need additional analysis
|
||
Description: "Unread emails older than 1 month",
|
||
Action: "Review and either read, archive, or delete",
|
||
})
|
||
|
||
// Large attachments
|
||
items = append(items, AttentionItem{
|
||
Category: "Large Attachments",
|
||
Query: `has:attachment larger:10M`,
|
||
Count: 0, // Would need additional analysis
|
||
Description: "Emails with attachments larger than 10MB",
|
||
Action: "Review and download important files, then delete emails",
|
||
})
|
||
|
||
return items
|
||
}
|
||
|
||
func calculateStorageImpact(stats EmailStats, recommendations CleanupRecommendations) StorageStats {
|
||
deletionPotential := 0
|
||
archivePotential := 0
|
||
|
||
for _, rec := range recommendations.SafeToDelete {
|
||
deletionPotential += rec.Count
|
||
}
|
||
|
||
for _, rec := range recommendations.BulkArchive {
|
||
archivePotential += rec.Count
|
||
}
|
||
|
||
totalReduction := deletionPotential + archivePotential
|
||
percentageReduction := float64(totalReduction) / float64(stats.TotalEmails) * 100
|
||
|
||
return StorageStats{
|
||
TotalEmails: stats.TotalEmails,
|
||
DeletionPotential: deletionPotential,
|
||
ArchivePotential: archivePotential,
|
||
PercentageReduction: percentageReduction,
|
||
CategoryBreakdown: stats.Categories,
|
||
}
|
||
}
|
||
|
||
func countSocialEmails(domains []DomainInfo) int {
|
||
count := 0
|
||
for _, domain := range domains {
|
||
if domain.Type == "social" {
|
||
count += domain.Count
|
||
}
|
||
}
|
||
return count
|
||
}
|
||
|
||
func countAutomatedEmails(domains []DomainInfo) int {
|
||
count := 0
|
||
for _, domain := range domains {
|
||
if domain.Type == "automated" {
|
||
count += domain.Count
|
||
}
|
||
}
|
||
return count
|
||
}
|
||
|
||
func containsSocialDomain(domain string) bool {
|
||
socialDomains := []string{"facebook", "twitter", "linkedin", "instagram", "youtube", "tiktok"}
|
||
domain = strings.ToLower(domain)
|
||
for _, social := range socialDomains {
|
||
if strings.Contains(domain, social) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func containsAutomatedKeywords(email string) bool {
|
||
keywords := []string{"noreply", "no-reply", "automated", "system", "admin"}
|
||
email = strings.ToLower(email)
|
||
for _, keyword := range keywords {
|
||
if strings.Contains(email, keyword) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func printSummary(recommendations CleanupRecommendations) {
|
||
fmt.Printf("\n=== INBOX CLEANUP RECOMMENDATIONS ===\n\n")
|
||
|
||
stats := recommendations.StorageStats
|
||
fmt.Printf("📊 CURRENT STATE:\n")
|
||
fmt.Printf(" Total emails: %d\n", stats.TotalEmails)
|
||
fmt.Printf(" Potential for deletion: %d emails\n", stats.DeletionPotential)
|
||
fmt.Printf(" Potential for archiving: %d emails\n", stats.ArchivePotential)
|
||
fmt.Printf(" Total inbox reduction: %.1f%%\n\n", stats.PercentageReduction)
|
||
|
||
fmt.Printf("🗑️ SAFE TO DELETE (%d emails):\n", stats.DeletionPotential)
|
||
for i, rec := range recommendations.SafeToDelete {
|
||
fmt.Printf(" %d. %s (%d emails, %s risk)\n", i+1, rec.Category, rec.Count, rec.Risk)
|
||
fmt.Printf(" Query: %s\n", rec.Query)
|
||
}
|
||
|
||
fmt.Printf("\n📦 BULK ARCHIVE (%d emails):\n", stats.ArchivePotential)
|
||
for i, rec := range recommendations.BulkArchive {
|
||
fmt.Printf(" %d. %s (%d emails)\n", i+1, rec.Category, rec.Count)
|
||
fmt.Printf(" Query: %s\n", rec.Query)
|
||
}
|
||
|
||
fmt.Printf("\n✋ UNSUBSCRIBE TARGETS:\n")
|
||
for i, target := range recommendations.UnsubscribeTargets {
|
||
if i >= 5 {
|
||
fmt.Printf(" ... and %d more (use 'detailed' output for full list)\n", len(recommendations.UnsubscribeTargets)-5)
|
||
break
|
||
}
|
||
fmt.Printf(" %d. %s (%d emails, %s priority)\n", i+1, target.Sender, target.Count, target.Priority)
|
||
}
|
||
|
||
fmt.Printf("\n⚠️ NEEDS ATTENTION:\n")
|
||
for i, item := range recommendations.AttentionNeeded {
|
||
fmt.Printf(" %d. %s\n", i+1, item.Description)
|
||
fmt.Printf(" Action: %s\n", item.Action)
|
||
}
|
||
|
||
fmt.Printf("\n💡 NEXT STEPS:\n")
|
||
fmt.Printf(" 1. Review deletion candidates (start with low-risk items)\n")
|
||
fmt.Printf(" 2. Set up bulk archive operations\n")
|
||
fmt.Printf(" 3. Unsubscribe from high-priority senders\n")
|
||
fmt.Printf(" 4. Create Gmail filters to prevent future buildup\n")
|
||
fmt.Printf(" 5. Address attention items\n\n")
|
||
fmt.Printf("Use 'detailed' output format for complete Gmail queries and instructions.\n")
|
||
}
|
||
|
||
func printDetailed(recommendations CleanupRecommendations) {
|
||
printSummary(recommendations)
|
||
|
||
fmt.Printf("\n=== DETAILED CLEANUP INSTRUCTIONS ===\n\n")
|
||
|
||
fmt.Printf("🗑️ DELETION INSTRUCTIONS:\n")
|
||
for i, rec := range recommendations.SafeToDelete {
|
||
fmt.Printf("\n%d. %s (%s risk)\n", i+1, rec.Category, rec.Risk)
|
||
fmt.Printf(" Gmail Query: %s\n", rec.Query)
|
||
fmt.Printf(" Description: %s\n", rec.Description)
|
||
fmt.Printf(" Estimated Count: %d emails\n", rec.Count)
|
||
fmt.Printf(" Examples: %s\n", strings.Join(rec.Examples, ", "))
|
||
fmt.Printf(" Instructions:\n")
|
||
fmt.Printf(" 1. Paste query into Gmail search\n")
|
||
fmt.Printf(" 2. Review a few emails to confirm they're safe to delete\n")
|
||
fmt.Printf(" 3. Select all → Delete\n")
|
||
}
|
||
|
||
fmt.Printf("\n📦 ARCHIVE INSTRUCTIONS:\n")
|
||
for i, rec := range recommendations.BulkArchive {
|
||
fmt.Printf("\n%d. %s\n", i+1, rec.Category)
|
||
fmt.Printf(" Gmail Query: %s\n", rec.Query)
|
||
fmt.Printf(" Description: %s\n", rec.Description)
|
||
fmt.Printf(" Reason: %s\n", rec.Reason)
|
||
fmt.Printf(" Estimated Count: %d emails\n", rec.Count)
|
||
fmt.Printf(" Instructions:\n")
|
||
fmt.Printf(" 1. Paste query into Gmail search\n")
|
||
fmt.Printf(" 2. Select all → Archive\n")
|
||
}
|
||
|
||
fmt.Printf("\n✋ UNSUBSCRIBE DETAILS:\n")
|
||
for i, target := range recommendations.UnsubscribeTargets {
|
||
fmt.Printf("\n%d. %s (%s priority)\n", i+1, target.Sender, target.Priority)
|
||
fmt.Printf(" Email Count: %d\n", target.Count)
|
||
fmt.Printf(" Category: %s\n", target.Category)
|
||
fmt.Printf(" Reason: %s\n", target.Reason)
|
||
fmt.Printf(" Gmail Query: from:%s\n", target.Sender)
|
||
}
|
||
}
|
||
|
||
func outputJSON(recommendations CleanupRecommendations) {
|
||
encoder := json.NewEncoder(os.Stdout)
|
||
encoder.SetIndent("", " ")
|
||
encoder.Encode(recommendations)
|
||
} |