124 lines
2.6 KiB
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)
|
|
}
|