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

500 lines
16 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}