diff --git a/scrape.go b/scrape.go index 00a50c6..7a7da4f 100755 --- a/scrape.go +++ b/scrape.go @@ -67,27 +67,57 @@ func containsAny(a string, b ...string) bool { } func (c *chaseScraper) scrape(m *mail.Message) ([]*Transaction, error) { + transactions, err := c.scrape2020(m) + if err == nil && len(transactions) > 0 { + return transactions, err + } + return c.scrape2021(m) +} + +func (c *chaseScraper) scrape2021(m *mail.Message) ([]*Transaction, error) { + re := regexp.MustCompile(`^Your \$(?P[0-9\.]*) transaction with (?P.*)$`) + matches := re.FindSubmatch([]byte(m.Header["Subject"][0])) + if len(matches) < 1 { + return nil, errors.New("no match subject search") + } + amount := string(matches[1]) + vendor := string(matches[2]) + + b, _ := ioutil.ReadAll(m.Body) + re = regexp.MustCompile(`\(\.\.\.[0-9]{4}\)`) + match := re.Find(b) + re = regexp.MustCompile(`[0-9]{4}`) + account := string(re.Find(match)) + + return []*Transaction{NewTransaction(account, amount, vendor, fmt.Sprint(m.Header["Date"]), Chase)}, nil +} + +func (c *chaseScraper) scrape2020(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) + re := regexp.MustCompile(`A charge of \([^)]*\) (?P[\d\.]+) at (?P.*) has been authorized`) + matches := re.FindSubmatch(b) if len(matches) < 2 { return nil, fmt.Errorf("no full matches found") } results := make(map[string][]string) - for i, name := range regexp.SubexpNames() { + for i, name := range re.SubexpNames() { if i != 0 && name != "" { results[name] = append(results[name], string(matches[i])) } } - if len(results) != 2 || len(results["amount"]) != len(results["account"]) { + if len(results) != 2 || len(results["amount"]) != len(results["vendor"]) { return nil, fmt.Errorf("unexpected matches found looking for transactions: %+v", results) } + re = regexp.MustCompile(`account ending in (?P[0-9]{4})\.`) + match := re.Find(b) + re = regexp.MustCompile(`[0-9]{4}`) + account := string(re.Find(match)) transactions := make([]*Transaction, len(results["amount"])) for i := range results["amount"] { - transactions[i] = NewTransaction(results["amount"][i], results["account"][i], fmt.Sprint(m.Header["Date"]), Chase) + transactions[i] = NewTransaction(account, results["amount"][i], results["vendor"][i], fmt.Sprint(m.Header["Date"]), Chase) } return transactions, nil } @@ -109,7 +139,7 @@ func (c *citiScraper) scrape(m *mail.Message) ([]*Transaction, error) { vendor := bytes.Split(bytes.Split(match, []byte(" on card ending in"))[0], []byte("transaction was made at "))[1] - transaction := NewTransaction(string(price), string(vendor), date, Citi) + transaction := NewTransaction(Citi.String(), string(price), string(vendor), date, Citi) return []*Transaction{transaction}, nil //Citi Alert: A $598.14 transaction was made at REMIX MUSIC SPRINGDA on card ending in 3837 @@ -147,7 +177,7 @@ func (c *citiScraper) scrape(m *mail.Message) ([]*Transaction, error) { transactions := make([]*Transaction, len(results["amount"])) for i := range results["amount"] { - transactions[i] = NewTransaction(results["amount"][i], results["account"][i], fmt.Sprint(m.Header["Date"]), Citi) + transactions[i] = NewTransaction(Citi.String(), results["amount"][i], results["account"][i], fmt.Sprint(m.Header["Date"]), Citi) } return transactions, nil @@ -173,6 +203,6 @@ func (c *uccuScraper) scrape(m *mail.Message) ([]*Transaction, error) { if !bytes.Contains(b, []byte("credit")) { f *= -1.0 } - transaction := NewTransaction(fmt.Sprintf("%.2f", f), "?", fmt.Sprint(m.Header["Date"]), UCCU) + transaction := NewTransaction(UCCU.String(), fmt.Sprintf("%.2f", f), "?", fmt.Sprint(m.Header["Date"]), UCCU) return []*Transaction{transaction}, nil } diff --git a/scrape_test.go b/scrape_test.go new file mode 100644 index 0000000..d1d4d53 --- /dev/null +++ b/scrape_test.go @@ -0,0 +1,75 @@ +package main + +import ( + "bytes" + "io/ioutil" + "net/mail" + "testing" +) + +func TestScrapeChase2021(t *testing.T) { + b, err := ioutil.ReadFile("./testdata/chase.2021.txt") + if err != nil { + t.Fatal(err) + } + message := &mail.Message{ + Header: map[string][]string{ + "Subject": []string{"Your $38.84 transaction with TARGET T-1754"}, + }, + Body: bytes.NewReader(b), + } + + chase := &chaseScraper{} + + gots, err := chase.scrape2021(message) + if err != nil { + t.Fatal(err) + } + if len(gots) != 1 { + t.Fatal(gots) + } + got := gots[0] + + if got.Account != "8824" { + t.Fatalf("bad account: %v: %+v", got.Account, got) + } + if got.Amount != "38.84" { + t.Fatalf("bad amount: %v: %+v", got.Amount, got) + } + if got.Vendor != "TARGET T-1754" { + t.Fatalf("bad vendor: %v: %+v", got.Vendor, got) + } + t.Logf("%+v", got) +} + +func TestScrapeChase2020(t *testing.T) { + b, err := ioutil.ReadFile("./testdata/chase.2020.txt") + if err != nil { + t.Fatal(err) + } + message := &mail.Message{ + Body: bytes.NewReader(b), + } + + chase := &chaseScraper{} + + gots, err := chase.scrape2020(message) + if err != nil { + t.Fatal(err) + } + if len(gots) != 1 { + t.Fatal(gots) + } + got := gots[0] + + if got.Account != "8824" { + t.Fatalf("bad account: %v: %+v", got.Account, got) + } + if got.Amount != "16.08" { + t.Fatalf("bad amount: %v: %+v", got.Amount, got) + } + if got.Vendor != "PAYPAL *BLIZZARDENT" { + t.Fatalf("bad vendor: %q: %+v", got.Vendor, got) + } + t.Logf("%+v", got) +} diff --git a/testdata/chase.2020.txt b/testdata/chase.2020.txt new file mode 100644 index 0000000..a3f280a --- /dev/null +++ b/testdata/chase.2020.txt @@ -0,0 +1,10 @@ +This is an Alert to help you manage your credit card account ending in 8824. + +As you requested, we are notifying you of any charges over the amount of ($USD) 0.00, as specified in your Alert settings. +A charge of ($USD) 16.08 at PAYPAL *BLIZZARDENT has been authorized on Jul 6, 2021 at 6:21 PM ET. + +Do not reply to this Alert. + +If you have questions, please call the number on the back of your credit card, or send a secure message from your Inbox on www.chase.com. + +To see all of the Alerts available to you, or to manage your Alert settings, please log on to www.chase.com. diff --git a/testdata/chase.2021.txt b/testdata/chase.2021.txt new file mode 100644 index 0000000..d57afc7 --- /dev/null +++ b/testdata/chase.2021.txt @@ -0,0 +1,396 @@ + + + + + +This transaction is above the level you set, see more here. + + + + + + + + + + + + + =20 +
+ +
+ + + + + +
3D""/ + + + +
+ +
This transaction is above the level you set, see more here.= +
+ + + +
 ‌ ‌ ‌ ‌ &zwn= +j; ‌ ‌ ‌ ‌ ‌ ‌&= +nbsp;‌ ‌  ‌ ‌ ‌ &zwn= +j; ‌ ‌ ‌ ‌ ‌ ‌&= +nbsp;‌ ‌ ‌ ‌ ‌ ‌&nbs= +p;‌ ‌ ‌ ‌ ‌ ‌ &= +zwnj; ‌ ‌ ‌ ‌ ‌ &zwn= +j; ‌ ‌ ‌ ‌ ‌ ‌&= +nbsp;‌ ‌ ‌ ‌ ‌ ‌&nbs= +p;‌ ‌ ‌ ‌ ‌ ‌ &= +zwnj; ‌ ‌ ‌ ‌  ‌&nbs= +p;‌ ‌ ‌ ‌ ‌ ‌ &= +zwnj; ‌ ‌ ‌ ‌ ‌ &zwn= +j; ‌ ‌ ‌ ‌ ‌ ‌&= +nbsp;‌ ‌ ‌ ‌ ‌ ‌&nbs= +p;‌ ‌ ‌ ‌ ‌ ‌ &= +zwnj; ‌ ‌ ‌ ‌ ‌ &zwn= +j; 
+ + + + + + + + + + +
+ + + +
3D"Chase
+ + + +
+ + + +
Transaction alert
+ + + +
+ + + + +
3D""= +You made a $38.84 transaction
3D""/
+ + + + + +
3D""/ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + +
AccountAARP fro= +m Chase (...8824)
+ + + + +
Date= +Jul 23, 2021 at 3:06 PM ET
+ + + + +
MerchantTARGET T= +-1754
+ + + +
Amount$38.84 +
You are receiving this alert beca= +use your transaction was more than the $0.00 level you set. You can visit o= +ur Resource Center anytime to help answer yo= +ur questions or manage your account.
+ + + +
Review account
Securely access your accounts with = +the Chase Mobile® app or chase.com.
+ + + +
About this message
+
+ Chase Mobile®=C2=A0app is available f= +or select mobile devices. Message and data rates may apply.
+
+ This service email was sent based on your ale= +rt settings. Use the Chase Mobile app or visit chase.com/alerts to view or manage your settings.
+
+ Chase cannot guarantee the delivery of alerts= + and notifications.=C2=A0Wireless or internet service provider outages or o= +ther circumstances could delay them. You can always check chase.com or the Chase Mobile=C2=A0app for the status of your accounts= + including your latest account balances and transaction details.=C2=A0
+
+ To protect your personal information, please = +don't reply to this message. Chase won't ask for confidential information i= +n an email.
+
+ If you have concerns about the authenticity o= +f this message or have questions about your account visit
chase.com= +/CustomerService for ways to contact us.
+
+ Your privacy is important to us. See our onli= +ne Security Center to learn how to protect your infor= +mation.
+
+ © 2021 JPMorgan Chase & Co.
3D""/
+ + + + diff --git a/transaction.go b/transaction.go index aa1e320..39f0de7 100755 --- a/transaction.go +++ b/transaction.go @@ -9,28 +9,30 @@ import ( ) type Transaction struct { - ID string - Bank Bank - Amount string - Vendor string - Date string + ID string + Bank Bank + Amount string + Vendor string + Date string + Account string } func (t *Transaction) Format() string { - return fmt.Sprintf("(%s) %v: %s @ %s", cleanDate(t.Date), t.Bank, t.Amount, t.Vendor) + return fmt.Sprintf("(%s) %v/%v: %s @ %s", cleanDate(t.Date), t.Account, t.Bank, t.Amount, t.Vendor) } func (t *Transaction) String() string { return fmt.Sprint(*t) } -func NewTransaction(amount, account, date string, bank Bank) *Transaction { +func NewTransaction(account, amount, vendor, date string, bank Bank) *Transaction { regexp := regexp.MustCompile(`\s\s+`) t := &Transaction{ - Amount: regexp.ReplaceAllString(amount, " "), - Vendor: regexp.ReplaceAllString(account, " "), - Bank: bank, - Date: date, + Account: account, + Amount: regexp.ReplaceAllString(amount, " "), + Vendor: regexp.ReplaceAllString(vendor, " "), + Bank: bank, + Date: date, } t.ID = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprint(t)))) return t diff --git a/transaction_test.go b/transaction_test.go index 7aab8b6..31aa3a6 100755 --- a/transaction_test.go +++ b/transaction_test.go @@ -3,7 +3,7 @@ package main import "testing" func TestTransactionFormat(t *testing.T) { - x := NewTransaction("12.34", "Amazon", "[Wed, 1 Apr 2020 10:14:11 -0400 (EDT)]", Chase) + x := NewTransaction("me", "12.34", "Amazon", "[Wed, 1 Apr 2020 10:14:11 -0400 (EDT)]", Chase) t.Logf("%s", x.String()) t.Logf("%s", x.Format()) }