package main import ( "errors" "fmt" "io/ioutil" "log" "net/mail" "regexp" "strings" "github.com/google/uuid" ) type scraper interface { scrape(*mail.Message) ([]*Transaction, error) } type chaseScraper struct{} type citiScraper struct{} func Scrape(m *mail.Message) ([]*Transaction, error) { scraper, err := buildScraper(m) if err != nil { return nil, err } return scraper.scrape(m) } func buildScraper(m *mail.Message) (scraper, error) { subject := fmt.Sprint(m.Header["Subject"]) if !containsAny(subject, "transaction", "report", "Transaction") { return nil, errors.New("cannot build scraper for subject " + subject) } log.Printf("%+v", m.Header) from := fmt.Sprint(m.Header["From"]) if strings.Contains(from, "Chase") { return newChaseScraper(), nil } if strings.Contains(from, "Citi") { return newCitiScraper(), nil } return nil, errors.New("unknown sender: " + from) } func newChaseScraper() scraper { return &chaseScraper{} } func newCitiScraper() scraper { return &citiScraper{} } func containsAny(a string, b ...string) bool { for i := range b { if strings.Contains(a, b[i]) { return true } } return false } func (c *chaseScraper) scrape(m *mail.Message) ([]*Transaction, error) { b, err := ioutil.ReadAll(m.Body) if err != nil { return nil, err } regexp := regexp.MustCompile(`A charge of \([^)]*\) (?P[\d\.]+) at (?P.*) has been authorized`) matches := regexp.FindSubmatch(b) if len(matches) < 2 { return nil, fmt.Errorf("no matches found: %+v: %s", matches, b) } results := make(map[string][]string) for i, name := range regexp.SubexpNames() { if i != 0 && name != "" { results[name] = append(results[name], string(matches[i])) } } if len(results) != 2 || len(results["amount"]) != len(results["account"]) { return nil, fmt.Errorf("unexpected matches found looking for transactions: %+v", results) } transactions := make([]*Transaction, len(results["amount"])) for i := range results["amount"] { transactions[i] = &Transaction{ Amount: results["amount"][i], Account: results["account"][i], Bank: Chase, Date: fmt.Sprint(m.Header["Date"]), ID: uuid.New().String(), // TODO random based on date, amount, account, and bank so find dupes } } return transactions, nil } func (c *citiScraper) scrape(m *mail.Message) ([]*Transaction, error) { panic("not impl") }