parent
6f3bf1f6a4
commit
10bc441e1e
3
bank.go
3
bank.go
|
|
@ -6,10 +6,13 @@ const (
|
|||
Chase Bank = iota + 1
|
||||
Citi Bank = iota + 1
|
||||
UCCU Bank = iota + 1
|
||||
BankOfAmerica Bank = iota + 1
|
||||
)
|
||||
|
||||
func (b Bank) String() string {
|
||||
switch b {
|
||||
case BankOfAmerica:
|
||||
return "BankOfAmerica"
|
||||
case Chase:
|
||||
return "Chase"
|
||||
case Citi:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"local/args"
|
||||
"local/oauth2"
|
||||
"local/storage"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -55,7 +56,7 @@ func NewConfig() Config {
|
|||
as.Append(args.STRING, "todolist", "todo list", "")
|
||||
as.Append(args.STRING, "todotag", "todo tag", "expense")
|
||||
|
||||
as.Append(args.STRING, "banks", "uccu,citi,chase", "uccu,citi,chase")
|
||||
as.Append(args.STRING, "banks", "uccu,citi,chase,bankofamerica", "uccu,citi,chase,bankofamerica")
|
||||
as.Append(args.STRING, "accounts", "regex to include filter accounts", ".*")
|
||||
as.Append(args.STRING, "not-accounts", "regex to exclude filter accounts", "zzzzzz")
|
||||
|
||||
|
|
@ -91,11 +92,13 @@ func NewConfig() Config {
|
|||
Storage: storage,
|
||||
Uploader: ul,
|
||||
Banks: map[Bank]bool{
|
||||
BankOfAmerica: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(BankOfAmerica.String())),
|
||||
Chase: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Chase.String())),
|
||||
Citi: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Citi.String())),
|
||||
UCCU: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(UCCU.String())),
|
||||
},
|
||||
}
|
||||
log.Printf("config: %+v", config)
|
||||
|
||||
if config.Uploader == UploaderTodo {
|
||||
token := as.GetString("todotoken")
|
||||
|
|
|
|||
41
scrape.go
41
scrape.go
|
|
@ -15,6 +15,7 @@ type scraper interface {
|
|||
scrape(*mail.Message) ([]*Transaction, error)
|
||||
}
|
||||
|
||||
type bankOfAmericaScraper struct{}
|
||||
type chaseScraper struct{}
|
||||
type citiScraper struct{}
|
||||
type uccuScraper struct{}
|
||||
|
|
@ -36,6 +37,9 @@ func buildScraper(m *mail.Message, banks map[Bank]bool) (scraper, error) {
|
|||
if strings.Contains(from, "Chase") && banks[Chase] {
|
||||
return newChaseScraper(), nil
|
||||
}
|
||||
if strings.Contains(from, "Bank of America") && banks[BankOfAmerica] {
|
||||
return newBankOfAmericaScraper(), nil
|
||||
}
|
||||
if strings.Contains(from, "Citi") && banks[Citi] {
|
||||
return newCitiScraper(), nil
|
||||
}
|
||||
|
|
@ -45,6 +49,10 @@ func buildScraper(m *mail.Message, banks map[Bank]bool) (scraper, error) {
|
|||
return nil, errors.New("unknown sender: " + from)
|
||||
}
|
||||
|
||||
func newBankOfAmericaScraper() scraper {
|
||||
return &bankOfAmericaScraper{}
|
||||
}
|
||||
|
||||
func newChaseScraper() scraper {
|
||||
return &chaseScraper{}
|
||||
}
|
||||
|
|
@ -238,3 +246,36 @@ func (c *uccuScraper) scrape(m *mail.Message) ([]*Transaction, error) {
|
|||
transaction := NewTransaction(UCCU.String(), fmt.Sprintf("%.2f", f), "?", fmt.Sprint(m.Header["Date"]), UCCU)
|
||||
return []*Transaction{transaction}, nil
|
||||
}
|
||||
|
||||
func (c *bankOfAmericaScraper) scrape(m *mail.Message) ([]*Transaction, error) {
|
||||
subject := fmt.Sprint(m.Header["Subject"])
|
||||
if strings.Contains(subject, "Credit card transaction") {
|
||||
return c.scrapeCharge(m)
|
||||
}
|
||||
return nil, errors.New("not impl")
|
||||
}
|
||||
|
||||
func (c *bankOfAmericaScraper) scrapeCharge(m *mail.Message) ([]*Transaction, error) {
|
||||
b, err := ioutil.ReadAll(m.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
amount := ""
|
||||
acc := ""
|
||||
for _, line := range bytes.Split(b, []byte("\n")) {
|
||||
if amount == "" && bytes.HasPrefix(line, []byte("Amount: $")) {
|
||||
words := bytes.Split(bytes.TrimSpace(line), []byte(" "))
|
||||
lastword := words[len(words)-1][1:]
|
||||
escapedfloat := bytes.TrimPrefix(lastword, []byte("$"))
|
||||
fixEscape := bytes.ReplaceAll(escapedfloat, []byte("=2E"), []byte("."))
|
||||
amount = string(fixEscape)
|
||||
} else if acc == "" && bytes.HasPrefix(line, []byte("Where: ")) {
|
||||
acc = string(bytes.TrimSpace(bytes.TrimPrefix(line, []byte("Where: "))))
|
||||
}
|
||||
}
|
||||
if amount == "" || acc == "" {
|
||||
return nil, errors.New("no amount/account found")
|
||||
}
|
||||
transaction := NewTransaction(BankOfAmerica.String(), amount, acc, fmt.Sprint(m.Header["Date"]), BankOfAmerica)
|
||||
return []*Transaction{transaction}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,3 +108,38 @@ func TestScrapeChase2020(t *testing.T) {
|
|||
}
|
||||
t.Logf("%+v", got)
|
||||
}
|
||||
|
||||
func TestScrapeBofACharge(t *testing.T) {
|
||||
b, err := ioutil.ReadFile("./testdata/bofa.charge.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
message := &mail.Message{
|
||||
Header: map[string][]string{
|
||||
"Subject": []string{"Credit card transaction exceeds alert limit you set"},
|
||||
},
|
||||
Body: bytes.NewReader(b),
|
||||
}
|
||||
bofa := &bankOfAmericaScraper{}
|
||||
|
||||
gots, err := bofa.scrape(message)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(gots) != 1 {
|
||||
t.Fatal(len(gots))
|
||||
}
|
||||
got := gots[0]
|
||||
want := Transaction{
|
||||
ID: got.ID,
|
||||
Bank: BankOfAmerica,
|
||||
Amount: "75.08",
|
||||
Vendor: "PAYPAL GIBBDOGENTE MA",
|
||||
Date: "[]",
|
||||
Account: BankOfAmerica.String(),
|
||||
}
|
||||
if *got != want {
|
||||
t.Fatalf("want:\n\t%+v, got\n\t%+v", want, *got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
|
||||
Credit card transaction exceeds alert limit you set
|
||||
|
||||
National Education Association World Mas ending in 7522
|
||||
|
||||
Amount: $75=2E08
|
||||
Date: September 05, 2021
|
||||
Where: PAYPAL GIBBDOGENTE MA
|
||||
|
||||
View details by going to
|
||||
https://www=2Ebankofamerica=2Ecom/deeplink/redirect=2Ego?target=3Dbofasigni=
|
||||
n&screen=3DAccounts:Home&version=3D7=2E0=2E0
|
||||
|
||||
If you made this purchase or payment but don=27t recognize the amount,
|
||||
wait until the final purchase amount has posted before filing a dispute
|
||||
claim=2E
|
||||
|
||||
If you don=27t recognize this activity, please contact us at the number
|
||||
on the back of your card=2E
|
||||
|
||||
Did you know?
|
||||
You can choose how you get alerts from us including text messages and
|
||||
mobile notifications=2E Go to Alert Settings at
|
||||
https://www=2Ebankofamerica=2Ecom/deeplink/redirect=2Ego?target=3Dalerts_se=
|
||||
ttings&screen=3DAlerts:Home&gotoSetting=3Dtrue&version=3D7=2E1=2E0
|
||||
|
||||
|
||||
We'll never ask for your personal information such as SSN or ATM PIN in
|
||||
email messages=2E If you get an email that looks suspicious or you are not =
|
||||
|
||||
the intended recipient of this email, don't click on any links=2E Instead, =
|
||||
|
||||
forward to abuse@bankofamerica=2Ecom then delete it=2E
|
||||
|
||||
Please don't reply to this automatically generated service email=2E
|
||||
Read our Privacy Notice https://www=2Ebankofamerica=2Ecom/privacy/consumer-=
|
||||
privacy-notice=2Ego
|
||||
Equal Housing Lender: https://www=2Ebankofamerica=2Ecom/help/equalhousing=
|
||||
=2Ecfm
|
||||
Bank of America, N=2EA=2E Member FDIC
|
||||
(C) 2021 Bank of America Corporation
|
||||
|
||||
|
||||
|
||||
=20
|
||||
|
|
@ -22,7 +22,7 @@ func (t *Transaction) Format() string {
|
|||
}
|
||||
|
||||
func (t *Transaction) String() string {
|
||||
return fmt.Sprint(*t)
|
||||
return fmt.Sprintf("%+v", *t)
|
||||
}
|
||||
|
||||
func NewTransaction(account, amount, vendor, date string, bank Bank) *Transaction {
|
||||
|
|
|
|||
Loading…
Reference in New Issue