parent
6f3bf1f6a4
commit
10bc441e1e
9
bank.go
9
bank.go
|
|
@ -3,13 +3,16 @@ package main
|
||||||
type Bank int
|
type Bank int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Chase Bank = iota + 1
|
Chase Bank = iota + 1
|
||||||
Citi Bank = iota + 1
|
Citi Bank = iota + 1
|
||||||
UCCU Bank = iota + 1
|
UCCU Bank = iota + 1
|
||||||
|
BankOfAmerica Bank = iota + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b Bank) String() string {
|
func (b Bank) String() string {
|
||||||
switch b {
|
switch b {
|
||||||
|
case BankOfAmerica:
|
||||||
|
return "BankOfAmerica"
|
||||||
case Chase:
|
case Chase:
|
||||||
return "Chase"
|
return "Chase"
|
||||||
case Citi:
|
case Citi:
|
||||||
|
|
|
||||||
11
config.go
11
config.go
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"local/args"
|
"local/args"
|
||||||
"local/oauth2"
|
"local/oauth2"
|
||||||
"local/storage"
|
"local/storage"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -55,7 +56,7 @@ func NewConfig() Config {
|
||||||
as.Append(args.STRING, "todolist", "todo list", "")
|
as.Append(args.STRING, "todolist", "todo list", "")
|
||||||
as.Append(args.STRING, "todotag", "todo tag", "expense")
|
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, "accounts", "regex to include filter accounts", ".*")
|
||||||
as.Append(args.STRING, "not-accounts", "regex to exclude filter accounts", "zzzzzz")
|
as.Append(args.STRING, "not-accounts", "regex to exclude filter accounts", "zzzzzz")
|
||||||
|
|
||||||
|
|
@ -91,11 +92,13 @@ func NewConfig() Config {
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Uploader: ul,
|
Uploader: ul,
|
||||||
Banks: map[Bank]bool{
|
Banks: map[Bank]bool{
|
||||||
Chase: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Chase.String())),
|
BankOfAmerica: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(BankOfAmerica.String())),
|
||||||
Citi: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Citi.String())),
|
Chase: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Chase.String())),
|
||||||
UCCU: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(UCCU.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 {
|
if config.Uploader == UploaderTodo {
|
||||||
token := as.GetString("todotoken")
|
token := as.GetString("todotoken")
|
||||||
|
|
|
||||||
41
scrape.go
41
scrape.go
|
|
@ -15,6 +15,7 @@ type scraper interface {
|
||||||
scrape(*mail.Message) ([]*Transaction, error)
|
scrape(*mail.Message) ([]*Transaction, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bankOfAmericaScraper struct{}
|
||||||
type chaseScraper struct{}
|
type chaseScraper struct{}
|
||||||
type citiScraper struct{}
|
type citiScraper struct{}
|
||||||
type uccuScraper 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] {
|
if strings.Contains(from, "Chase") && banks[Chase] {
|
||||||
return newChaseScraper(), nil
|
return newChaseScraper(), nil
|
||||||
}
|
}
|
||||||
|
if strings.Contains(from, "Bank of America") && banks[BankOfAmerica] {
|
||||||
|
return newBankOfAmericaScraper(), nil
|
||||||
|
}
|
||||||
if strings.Contains(from, "Citi") && banks[Citi] {
|
if strings.Contains(from, "Citi") && banks[Citi] {
|
||||||
return newCitiScraper(), nil
|
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)
|
return nil, errors.New("unknown sender: " + from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBankOfAmericaScraper() scraper {
|
||||||
|
return &bankOfAmericaScraper{}
|
||||||
|
}
|
||||||
|
|
||||||
func newChaseScraper() scraper {
|
func newChaseScraper() scraper {
|
||||||
return &chaseScraper{}
|
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)
|
transaction := NewTransaction(UCCU.String(), fmt.Sprintf("%.2f", f), "?", fmt.Sprint(m.Header["Date"]), UCCU)
|
||||||
return []*Transaction{transaction}, nil
|
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)
|
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 {
|
func (t *Transaction) String() string {
|
||||||
return fmt.Sprint(*t)
|
return fmt.Sprintf("%+v", *t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransaction(account, amount, vendor, date string, bank Bank) *Transaction {
|
func NewTransaction(account, amount, vendor, date string, bank Bank) *Transaction {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue