whatsviewer/zip.go

124 lines
2.6 KiB
Go

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)
}