package main import ( "archive/zip" "crypto/sha256" "fmt" "io" "os" "path/filepath" "strings" ) func scanDirectory(dir string) (map[string]*Conversation, error) { conversations := make(map[string]*Conversation) entries, err := os.ReadDir(dir) if err != nil { return nil, fmt.Errorf("reading directory: %w", err) } for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() // Skip macOS resource fork files if strings.HasPrefix(name, "._") { continue } // Accept .zip files or files named "WhatsApp Chat with *" (no extension) isZip := strings.HasSuffix(strings.ToLower(name), ".zip") isWhatsApp := strings.HasPrefix(name, "WhatsApp Chat with ") if !isZip && !isWhatsApp { continue } fullPath := filepath.Join(dir, name) conv, err := loadConversation(fullPath) if err != nil { fmt.Fprintf(os.Stderr, "warning: skipping %s: %v\n", name, err) continue } conversations[conv.ID] = conv } return conversations, nil } func makeID(path string) string { h := sha256.Sum256([]byte(path)) return fmt.Sprintf("%x", h[:8]) } func loadConversation(zipPath string) (*Conversation, error) { r, err := zip.OpenReader(zipPath) if err != nil { return nil, fmt.Errorf("opening zip: %w", err) } defer r.Close() var chatContent string for _, f := range r.File { if strings.HasSuffix(f.Name, ".txt") { rc, err := f.Open() if err != nil { return nil, fmt.Errorf("opening txt: %w", err) } data, err := io.ReadAll(rc) rc.Close() if err != nil { return nil, fmt.Errorf("reading txt: %w", err) } chatContent = string(data) break } } if chatContent == "" { return nil, fmt.Errorf("no .txt file found in zip") } // Derive contact name from zip filename base := filepath.Base(zipPath) base = strings.TrimSuffix(base, ".zip") contactName := strings.TrimPrefix(base, "WhatsApp Chat with ") messages := parseChat(chatContent) conv := &Conversation{ ID: makeID(zipPath), Name: contactName, Messages: messages, MessageCount: len(messages), } if len(messages) > 0 { conv.LastMessageDate = messages[len(messages)-1].Timestamp } return conv, nil } func getMediaFromZip(zipPath string, filename string) ([]byte, error) { r, err := zip.OpenReader(zipPath) if err != nil { return nil, err } defer r.Close() // Case-insensitive matching lowerFilename := strings.ToLower(filename) for _, f := range r.File { if strings.ToLower(f.Name) == lowerFilename { rc, err := f.Open() if err != nil { return nil, err } defer rc.Close() return io.ReadAll(rc) } } return nil, fmt.Errorf("file not found in zip: %s", filename) }