diff --git a/cmd/abook-lookup/abook-lookup b/cmd/abook-lookup/abook-lookup new file mode 100755 index 0000000..a08d3f2 --- /dev/null +++ b/cmd/abook-lookup/abook-lookup @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +echo "Searching for $1" +sqlite3 -list -separator ' ' ~/.mutt/abook.db << EOSQL +SELECT s.email, s.name, s.frequency +FROM search s +INNER JOIN + (SELECT email, max(frequency) AS maxfrequency FROM search GROUP BY email) m +ON s.email = m.email AND s.frequency = m.maxfrequency +WHERE s.name +LIKE '%$1%' OR s.email LIKE '%$1%' +ORDER BY s.frequency DESC; +EOSQL diff --git a/cmd/extract_accounts/extract_accounts.go b/cmd/extract_accounts/extract_accounts.go new file mode 100644 index 0000000..7b7ddab --- /dev/null +++ b/cmd/extract_accounts/extract_accounts.go @@ -0,0 +1,190 @@ +package main + +import ( + "database/sql" + "flag" + "fmt" + "log" + "net/mail" + "os" + "path/filepath" + "strings" + + _ "github.com/mattn/go-sqlite3" +) + +var ( + quiet = flag.Bool("q", false, "don't print status messages to stdout") + debugLogFn = flag.String("debugLogFn", "/dev/null", "file to store debugging logs") + sentDirGlob = flag.String("sentGlob", "${HOME}/Maildir/.Sent/*/*", + "Pattern for email sent files") + addressDbFn = flag.String("addressDbFn", "${HOME}/.mutt/abook.db", + "Location to store sqlite database of contacts") +) + +type Contact struct { + mail.Address + fn string +} + +func getAddresses(fn string, results chan<- *Contact) { + file, err := os.Open(fn) + if err != nil { + log.Fatalf("Failed to open %q: %s\n", fn, err) + } + + msg, err := mail.ReadMessage(file) + if err != nil { + log.Printf("Failed to parse message %q: %s\n", fn, err) + return + } + + headers := []string{"to", "cc", "resent-to", "resent-cc", "bcc"} + for _, header := range headers { + addrs, err := msg.Header.AddressList(header) + if err != nil { + //log.Printf("Failed to parse %s: header in %q: %s", header, fn, err) + continue + } + + for _, addr := range addrs { + c := &Contact{*addr, fn} + results <- c + } + } +} + +func getEmails(results chan<- *Contact) { + + files, err := filepath.Glob(os.ExpandEnv(*sentDirGlob)) + if !(*quiet) { + fmt.Printf("Scanning %d files\n", len(files)) + } + + if err != nil { + log.Fatalf("Failed to glob %q: %s\n", *sentDirGlob, err) + } + + for _, fn := range files { + getAddresses(fn, results) + } + close(results) +} + +func createEmailTable(db *sql.DB) { + sqls := []string{ + "DROP TABLE IF EXISTS emails", + "CREATE TABLE emails (email, name, fn)", + } + + for _, sql := range sqls { + _, err := db.Exec(sql) + if err != nil { + log.Fatalf("Error initializing DB %q: %s\n", err, sql) + } + } + +} + +func createSearchTable(db *sql.DB) { + sqls := []string{ + "DROP TABLE IF EXISTS search", + `CREATE TABLE search AS + SELECT email, name, count(*) AS frequency + FROM emails group BY name, email + ORDER BY frequency`, + } + + for _, sql := range sqls { + _, err := db.Exec(sql) + if err != nil { + log.Fatalf("Error creating search table %q: %s\n", err, sql) + } + } +} + +func fillEmailTable(db *sql.DB, results <-chan *Contact) { + tx, err := db.Begin() + if err != nil { + log.Fatalf("Faild to being transaction: %s\n", err) + return + } + defer tx.Commit() + + stmt, err := tx.Prepare("INSERT INTO emails VALUES (?, ?, ?)") + defer stmt.Close() + if err != nil { + log.Fatalf("Error preparing statement: %s\n", err) + return + } + + count := 1 + for c := range results { + addr := c.Address + if !(*quiet) { + if 0 == count%50 { + fmt.Print(count) + } + + if 0 == count%10 { + fmt.Print(".") + } + } + count += 1 + + var name string + if addr.Name != "" { + name = addr.Name + if -1 != strings.Index(name, ",") { + parts := strings.SplitN(name, ",", 2) + first, second := strings.TrimSpace(parts[1]), parts[0] + name = fmt.Sprintf("%s %s", first, second) + } + } else { + name = addr.Address + } + address := strings.ToLower(addr.Address) + + _, err = stmt.Exec(address, name, c.fn) + if err != nil { + log.Fatalf("Failed to insert value: %s\n", err) + } + } +} + +func buildDb(results <-chan *Contact) { + os.Remove(os.ExpandEnv(*addressDbFn)) + + db, err := sql.Open("sqlite3", os.ExpandEnv(*addressDbFn)) + if err != nil { + fmt.Println(err) + return + } + defer db.Close() + + createEmailTable(db) + fillEmailTable(db, results) + createSearchTable(db) +} + +func init() { + flag.Parse() +} + +func main() { + logFile, err := os.OpenFile(*debugLogFn, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatalf("Failed to open log %q: %s\n", *debugLogFn, err) + } + defer logFile.Close() + log.SetOutput(logFile) + log.Println("Started") + + results := make(chan *Contact) + + go getEmails(results) + + buildDb(results) + + log.Println("Finished successfully") +}