diff --git a/bank.go b/bank.go new file mode 100644 index 0000000..e595485 --- /dev/null +++ b/bank.go @@ -0,0 +1,18 @@ +package main + +type Bank int + +const ( + Chase Bank = iota + 1 + Citi Bank = iota + 1 +) + +func (b Bank) String() string { + switch b { + case Chase: + return "Chase" + case Citi: + return "Citi" + } + return "?" +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..790691e --- /dev/null +++ b/config.go @@ -0,0 +1,32 @@ +package main + +import "local/args" + +type Config struct { + EmailUser string + EmailPass string + EmailIMAP string + TodoAddr string + TodoPass string +} + +func NewConfig() Config { + as := args.NewArgSet() + + as.Append(args.STRING, "emailuser", "email username", "breellocaldev@gmail.com") + as.Append(args.STRING, "emailpass", "email password", "ML3WQRFSqe9rQ8qNkm") + as.Append(args.STRING, "emailimap", "email imap", "imap.gmail.com:993") + as.Append(args.STRING, "todoaddr", "todo addr", "https://todo-server.remote.blapointe.com") + as.Append(args.STRING, "todopass", "todo pass", "gJtEXbbLHLf54yS9EdujtVN2n6Y") + + if err := as.Parse(); err != nil { + panic(err) + } + return Config{ + EmailUser: as.GetString("emailuser"), + EmailPass: as.GetString("emailpass"), + EmailIMAP: as.GetString("emailimap"), + TodoAddr: as.GetString("todoaddr"), + TodoPass: as.GetString("todopass"), + } +} diff --git a/main.go b/main.go index f0d6e21..61b3531 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,23 @@ package main -import "log" +import ( + "local/sandbox/contact/contact" + "log" +) func main() { - log.Println("hello world") + config := NewConfig() + emailer := &contact.Emailer{ + IMAP: config.EmailIMAP, + From: config.EmailUser, + Password: config.EmailPass, + } + log.Println(emailer) + emails, err := emailer.Read() + if err != nil { + panic(err) + } + for _, email := range emails { + log.Println(Scrape(email)) + } } diff --git a/scrape.go b/scrape.go new file mode 100644 index 0000000..c2e759b --- /dev/null +++ b/scrape.go @@ -0,0 +1,97 @@ +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") +} diff --git a/transaction.go b/transaction.go new file mode 100644 index 0000000..898108b --- /dev/null +++ b/transaction.go @@ -0,0 +1,15 @@ +package main + +import "fmt" + +type Transaction struct { + ID string + Bank Bank + Amount string + Account string + Date string +} + +func (t *Transaction) String() string { + return fmt.Sprint(*t) +}